Executor框架(三)ScheduledExecutorService-和-BlockingQueue

本文已同步至liaosi's blog-Executor框架(三)ScheduledExecutorService-和-BlockingQueue

ScheduledExecutorService

ScheduledExecutorService是一个线程池,用来在指定延时之后执行或者以固定的频率周期性的执行提交的任务。它包含了4个方法。

public interface ScheduledExecutorService extends ExecutorService {

    /**
     * 在指定delay(延时)之后,执行提交Runnable的任务,返回一个ScheduledFuture,
     * 任务执行完成后ScheduledFuture的get()方法返回为null,ScheduledFuture的作用是可以cancel任务
     */
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    /**
     * 在指定delay(延时)之后,执行提交Callable的任务,返回一个ScheduledFuture
     */
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    /**
     * 提交一个Runnable任务延迟了initialDelay时间后,开始周期性的执行该任务,每period时间执行一次
     * 如果任务异常则退出。如果取消任务或者关闭线程池,任务也会退出。
     * 如果任务执行一次的时间大于周期时间,则任务执行将会延后执行,而不会并发执行
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    /**
     * 提交一个Runnable任务延迟了initialDelay时间后,开始周期性的执行该任务,以后
       每两次任务执行的间隔是delay
     * 如果任务异常则退出。如果取消任务或者关闭线程池,任务也会退出。
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

下面给出几个示例,加深ScheduledExecutorService几个方法的使用

schedule方法的测试代码:

    @Test
    public void testSchedule() {
        ScheduledExecutorService scheduledThreadPool = null;
        try {
            scheduledThreadPool = Executors.newScheduledThreadPool(4);
            SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
            System.out.println("submit a callable task to threadpool, current time:" + sdf.format(new Date()));
            ScheduledFuture<String> scheduledFuture = scheduledThreadPool.schedule(() -> {
                System.out.println("start to execute task, current time:" + sdf.format(new Date()));
                TimeUnit.SECONDS.sleep(3L);
                return "success";
            }, 2L, TimeUnit.SECONDS);
            System.out.println("result:" + scheduledFuture.get() + ", current time:" + sdf.format(new Date()));
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            if (scheduledThreadPool != null) {
                System.out.println("close the schedule threadpool!");
                scheduledThreadPool.shutdown();
            }
        }
    }

执行该方法会在控制台的输出:

submit a callable task to threadpool, current time:10:23:20
start to execute task, current time:10:23:22
result:success, current time:10:23:25
close the schedule threadpool!

scheduleAtFixedRate方法测试:

    @Test
    public void testFixedRate(){

        ScheduledExecutorService scheduledThreadPool = null;
        try {
            scheduledThreadPool = Executors.newScheduledThreadPool(4);
            SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
            System.out.println("submit a callable task to threadpool, current time:" + sdf.format(new Date()));
            ScheduledFuture<?> scheduledFuture = scheduledThreadPool.scheduleAtFixedRate(() -> {
                System.out.println(Thread.currentThread().getName() + " | start to execute task, current time :" + sdf.format(new Date()));
                try {
                    TimeUnit.SECONDS.sleep(2L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, 2L, 3L, TimeUnit.SECONDS);

            TimeUnit.SECONDS.sleep(15L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (scheduledThreadPool != null) {
                System.out.println("close the schedule threadpool!");
                scheduledThreadPool.shutdown();
            }
        }
    }

执行该方法会在控制台的输出:

submit a callable task to threadpool, current time:10:24:42
pool-1-thread-1 | start to execute task, current time :10:24:44
pool-1-thread-1 | start to execute task, current time :10:24:47
pool-1-thread-2 | start to execute task, current time :10:24:50
pool-1-thread-1 | start to execute task, current time :10:24:53
pool-1-thread-1 | start to execute task, current time :10:24:56
close the schedule threadpool!

scheduleWithFixedDelay方法测试

    @Test
    public void testFixedDelay(){

        ScheduledExecutorService scheduledThreadPool = null;
        try {
            scheduledThreadPool = Executors.newScheduledThreadPool(4);
            SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
            System.out.println("submit a callable task to threadpool, current time:" + sdf.format(new Date()));
            ScheduledFuture<?> scheduledFuture = scheduledThreadPool.scheduleWithFixedDelay(() -> {
                System.out.println(Thread.currentThread().getName() + " | start to execute task, current time :" + sdf.format(new Date()));
                try {
                    TimeUnit.SECONDS.sleep(3L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, 2L, 1L, TimeUnit.SECONDS);

            TimeUnit.SECONDS.sleep(20L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (scheduledThreadPool != null) {
                System.out.println("close the schedule threadpool!");
                scheduledThreadPool.shutdown();
            }
        }
    }

执行该方法会在控制台的输出:

submit a callable task to threadpool, current time:10:28:35
pool-1-thread-1 | start to execute task, current time :10:28:37
pool-1-thread-1 | start to execute task, current time :10:28:41
pool-1-thread-2 | start to execute task, current time :10:28:45
pool-1-thread-1 | start to execute task, current time :10:28:49
pool-1-thread-1 | start to execute task, current time :10:28:53
close the schedule threadpool!

BlockingQueue

BlockingQueue是java.util.concurrent包下的一个借口,继承了接口Queue,而Queue又继承自Collection接口。BlockingQueue是Executor框架中非常重要的一个类(提交到线程池中的任务就存储在BlockingQueue中)。

BlockingQueue是一个阻塞队列,何为阻塞(blocking)?所谓阻塞是指在某些情况下线程被挂起的现象。在BlockingQueue中阻塞主要有两层含义:

  • 从队列中获取元素时,如果队列为空,获取元素的线程会等待直至队列中有元素再返回。
  • 往队列中添加元素时,如果队列已满,添加元素的线程会等待至队列有位置可以添加新的元素。

BlockingQueue有多种插入、移除和获取元素的方法,这些方法对于阻塞的情形有4中不同的处理方式:

Throws exception Special value Bolcks Times out
Insert add(e) offer(e) put(e) offer(e,time,unit)
Remove remove(e) poll(e) take() poll(time,unit)
Retrieve element() peek()

上面表格里的内容不需要死记硬背,知道有这么回事,在需要用的时候根据自己的需要选取合适的方法即可。

BlockingQueue的特点
BlockingQueue中的元素不允许为null,在add、offer、put这三个添加元素的方法中,如果插入null会抛出NullPointerException异常。poll操作如果失败则会返回null,所以,如果允许插入null值的话,那获取的时候,就不能很好地用null来判断到底是代表失败,还是获取的值就是null值。

一个 BlockingQueue 可能是有界的,如果在插入的时候,发现队列满了,那么 put 操作将会阻塞。而我们说的BlockingQueue无界队列也不是说真正的无界,它的容量是 Integer.MAX_VALUE(21亿多)。

BlockingQueue 是设计用来实现生产者-消费者队列的,当然,你也可以将它当做普通的 Collection 来用,前面说了,它实现了java.util.Collection接口。例如,我们可以用remove(x)来删除任意一个元素,但是,这类操作通常并不高效,所以尽量只在少数的场合使用,比如一条消息已经入队,但是需要做取消操作的时候。

BlockingQueue 的实现都是线程安全的,但是批量的集合操作如addAll,containsAll, retainAllremoveAll不一定是原子操作。如addAll(c)有可能在添加了一些元素后中途抛出异常,此时 BlockingQueue 中已经添加了部分元素,这个是允许的,取决于具体的实现。

注意:BlockingQueue 在生产者-消费者的场景中,是支持多消费者和多生产者的。

BlockingQueue的几个核心方法

插入元素

  • boolean add(E e):如果队列中有空间,立即插入一个元素到队列里,并返回true(和Collection的add方法一样)。如果队列没有空间了,则抛出IllegalStateException异常。对于有界队列,更常用的是使用offer方法。
  • boolean offer(E e):如果队列中有空间,立即插入一个元素到队列里,并返回true,如果没有空间则返回false。
  • void put(E e):插入一个元素到队列中,如果该队列当前已满则该方法一直阻塞直至可以添加新的元素。
  • boolean offer(E e, long timeout, TimeUnit unit):插入一个元素到队列中,如果队列已满则等待timeout时长。插入成功返回true,超时则返回false。

获取元素

  • E take():取得队列头部的第一个元素,并将它从队列中移除。如果队列中没有元素则一直等待至有可获取的元素。
  • E poll(long timeout, TimeUnit unit):取得队列头部的第一个元素,并将它从队列中移除。如果队列中没有元素会等待timeout时长。

移除元素

  • boolean remove(Object o):从队列中移除指定的元素(当队列中存在这个元素时),判断方式是equals方法。如果移除的元素和队列中元素的类型不一样,则抛出ClassCastException异常。如果队列中有多个元素,则移除最先插入到队列中的那一个。

检索第一个元素

  • E remove():检索出队列中头部的第一个元素(并不会将其移除),如果队列为空则抛出NoSuchElementException异常。
  • E peek():和remove()方法作用一样。不同点是如果队列为空,不会抛出异常,而是返回null。
阅读 2.5k

推荐阅读
技术文档整理
用户专栏

后端开发

21 人关注
31 篇文章
专栏主页