DelayQueue实现的Comparable接口

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设置为静态内部类,则不含外部类的引用,因此不需要外部类存在也可以实例化静态内部类。所以一般情况下,内部类都是以静态内部类的形式存在的。

猜你喜欢

转载自blog.csdn.net/puppylpg/article/details/78650936