DelayQueue
DelayQueue是JDK对BlockingQueue接口的一种实现类。对于DelayQueue的介绍,可以参考一篇很不错的文章。
Comparable接口和Delayed接口
DelayQueue队列保存的元素必须实现Delayed接口,而Delayed接口又实现了Comparable接口 。
需要理解Delayed接口和Comparable接口的意义。详见Student类的注释。
/** * DelayQueue的元素必须实现{@link Delayed}接口(同时也实现了{@link Comparable})接口 * {@link Delayed#getDelay(TimeUnit)}决定了元素是否可以被取出 * {@link Comparable#compareTo(Object)}决定了元素在队列中的排列顺序,当很多元素都可以被取出的时候,就按这个顺序进行 */
可通过以下示例学习DelayQueue:
package example.concurrency.blockingqueue;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author liuhaibo on 2017/11/28
*/
public class UsageOfDelayQueue {
private static final int STUDENT_NUM = 10;
public static void main(String[] args) throws InterruptedException {
Random r = new Random();
DelayQueue<Student> students = new DelayQueue<>();
// to run Student
ExecutorService exec = Executors.newFixedThreadPool(STUDENT_NUM);
for (int i = 0; i < STUDENT_NUM; i++) {
students.put(new Student("Student" + i, 3000 + r.nextInt(5000)));
}
// start take object from delayQueue
while (!students.isEmpty()) {
// take a Student and execute since it's Runnable
exec.execute(students.take());
}
exec.shutdown();
}
/**
* DelayQueue的元素必须实现{@link Delayed}接口(同时也实现了{@link Comparable})接口
* {@link Delayed#getDelay(TimeUnit)}决定了元素是否可以被取出
* {@link Comparable#compareTo(Object)}决定了元素在队列中的排列顺序,当很多元素都可以被取出的时候,就按这个顺序进行
*/
private static class Student implements Runnable, Delayed {
private String name;
// time to use, assigned by Random
private long costTime;
// time to finish
private long finishedTime;
Student(String name, long costTime) {
this.name = name;
this.costTime = costTime;
finishedTime = costTime + System.currentTimeMillis();
}
@Override
public void run() {
System.out.println(name + " finished. Time used: " + costTime + "ms");
}
/**
* to judge the time after which object can be take out of the delay queue
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
return (finishedTime - System.currentTimeMillis());
}
/**
* to sort object in the delay queue
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
Student other = (Student) o;
return costTime >= other.costTime ? 1 : -1;
// return name.compareTo(other.name);
}
}
}
一种可能的输出结果如下:
Student3 finished. Time used: 3087ms
Student5 finished. Time used: 3334ms
Student8 finished. Time used: 3745ms
Student9 finished. Time used: 4215ms
Student1 finished. Time used: 4309ms
Student6 finished. Time used: 4773ms
Student7 finished. Time used: 5761ms
Student2 finished. Time used: 7151ms
Student4 finished. Time used: 7243ms
Student0 finished. Time used: 7855ms
DelayQueue先按照Comparable接口对所有元素进行排序,一般情况下,我们都是按元素过期时间的优先级进行排序。这里,即按照costTime进行排序,使得costTime小的排在队列的前面。由于getDelay()决定了该元素是否可以出列,因此finishedTime小的(即costTime小的)更早能拥有出列的资格。所以,最终成就了一种理想的情况:队列开头的元素更早拥有出列的资格,使得整个队列能够以最快的时间执行完毕。个人认为,而这也是DelayQueue引入Comparable接口的初衷。
与之相较,如果我们修改Student#CompareTo(Delayed o)
方法,使得其按照name排序(即注释掉的那行代码),则一种可能的输出如下:
Student0 finished. Time used: 7503ms
Student1 finished. Time used: 5281ms
Student2 finished. Time used: 3718ms
Student3 finished. Time used: 3322ms
Student4 finished. Time used: 3281ms
Student5 finished. Time used: 4981ms
Student6 finished. Time used: 3757ms
Student7 finished. Time used: 5313ms
Student8 finished. Time used: 6080ms
Student9 finished. Time used: 3112ms
可看出,程序是按照Student的name的顺序从DelayQueue中取出对象并执行的。这种情况,明显比之前按照costTime排序慢许多。究其原因,我们在DelayQueue中给Student对象按照Student的name进行排序,Student0需要7503ms才有出队的资格,而Student0~9都能比Studnet0更早拥有出队的资格,但是由于BlockingQueue的take()方法是取队首元素,如果队首不能出列,则所有Student都只能在队列里等待,不能被执行。
外话-线程池
值得一提的是,main函数里的线程池对加快程序运行速度并没有什么卵用。因为就算开启10个线程,在队首Student不具备出队资格时,无法取出任何一个Student。而每次一旦Student被取出,其执行任务也是打印一句话,瞬间就完成了,并不会导致一个线程运行多时。
为了看出线程池的效果,我们将Student的run()
方法在执行时阻塞1s,并在main()方法中记录程序总执行时间,再比较线程池和单线程的效果。修改后的程序如下:
package example.concurrency.blockingqueue;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author liuhaibo on 2017/11/28
*/
public class UsageOfDelayQueue {
private static final int STUDENT_NUM = 10;
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Random r = new Random();
DelayQueue<Student> students = new DelayQueue<>();
// to run Student
ExecutorService service = Executors.newFixedThreadPool(STUDENT_NUM);
// ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < STUDENT_NUM; i++) {
students.put(new Student("Student" + i, 2000 + r.nextInt(3000)));
}
// start take object from delayQueue
while (!students.isEmpty()) {
// take a Student and execute since it's Runnable
service.execute(students.take());
}
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
long stop = System.currentTimeMillis();
System.out.println("Total time used: " + (stop - start) + "ms.");
}
/**
* DelayQueue的元素必须实现{@link Delayed}接口(同时也实现了{@link Comparable})接口
* {@link Delayed#getDelay(TimeUnit)}决定了元素是否可以被取出
* {@link Comparable#compareTo(Object)}决定了元素在队列中的排列顺序,当很多元素都可以被取出的时候,就按这个顺序进行
*/
private static class Student implements Runnable, Delayed {
private String name;
// time to use, assigned by Random
private long costTime;
// time to finish
private long finishedTime;
Student(String name, long costTime) {
this.name = name;
this.costTime = costTime;
finishedTime = costTime + System.currentTimeMillis();
}
@Override
public void run() {
System.out.println(name + " finished. Time used: " + costTime + "ms");
synchronized (this) {
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + " done~");
}
/**
* to judge the time after which object can be take out of the delay queue
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
return (finishedTime - System.currentTimeMillis());
}
/**
* to sort object in the delay queue
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
Student other = (Student) o;
return costTime >= other.costTime ? 1 : -1;
// return name.compareTo(other.name);
}
}
}
一种可能的多线程执行情况如下:
Student9 finished. Time used: 2570ms
Student8 finished. Time used: 2695ms
Student1 finished. Time used: 3252ms
Student6 finished. Time used: 3361ms
Student3 finished. Time used: 3455ms
Student5 finished. Time used: 3469ms
Student9 done~
Student8 done~
Student7 finished. Time used: 4236ms
Student1 done~
Student6 done~
Student3 done~
Student5 done~
Student2 finished. Time used: 4539ms
Student0 finished. Time used: 4771ms
Student4 finished. Time used: 4979ms
Student7 done~
Student2 done~
Student0 done~
Student4 done~
Total time used: 5983ms.
可看到在第一个线程执行Student9还未结束时,第二个线程即取出并执行当前队首的元素Student8,等等,程序总共5.983s即执行完毕。
再看一种可能的单线程执行情况(启用被main函数注释掉的ExecutorService service = Executors.newSingleThreadExecutor()
):
Student4 finished. Time used: 2300ms
Student4 done~
Student1 finished. Time used: 2784ms
Student1 done~
Student5 finished. Time used: 3699ms
Student5 done~
Student0 finished. Time used: 3720ms
Student0 done~
Student3 finished. Time used: 3901ms
Student3 done~
Student8 finished. Time used: 3965ms
Student8 done~
Student7 finished. Time used: 4257ms
Student7 done~
Student2 finished. Time used: 4316ms
Student2 done~
Student9 finished. Time used: 4367ms
Student9 done~
Student6 finished. Time used: 4705ms
Student6 done~
Total time used: 12306ms.
可见,在Student4出队之后,线程将其执行完毕,才再去取出当前队首的Student1,并执行完毕,等等。最终程序耗时12.306s。
只有当需要执行的任务比较耗时时,多线程的好处才能体现出来。
外话-静态内部类
最后说句额外的:Student
作为UsageOfDelayQueue
类的内部类,需要设置成static
,即静态内部类,否则会报编译时错误:“non-static variable this cannot be referenced from a static context”
。
原因为我们在Student
中使用synchronized
锁住this
对象。然而,在main函数中,我们并没有实例化UsageOfDelayQueue
对象,所有的非静态内部类都要含有外部类的引用(或者说:指向外部类的指针),如果外部类的实例不存在,那么内部类的实例自然也不可能存在,因此this也无Student对象可指,所以会报错。
如果将Student设置为静态内部类,则不含外部类的引用,因此不需要外部类存在也可以实例化静态内部类。所以一般情况下,内部类都是以静态内部类的形式存在的。