Executor(二)ThreadPoolExecutor、ScheduledThreadPoolExecutor 及 Executors 工厂类

Executor(二)ThreadPoolExecutor、ScheduledThreadPoolExecutor 及 Executors 工厂类

Java 中的线程池类有两个,分别是:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor,这两个类都继承自 ExecutorService。利用这两个类,可以创建各种不同的Java线程池,为了方便我们创建线程池,Java API提供了 Executors 工厂类来帮助我们创建各种各样的线程池。下面我们分别介绍一下这三个类。

一、ThreadPoolExecutor

public ThreadPoolExecutor(
    int corePoolSize,       // 核心线程数,初始化的线程数
    int maximumPoolSize,    // 最大线程数
    long keepAliveTime,     // 空闲线程最大存活时间
    TimeUnit unit,          // 单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,        // 
    RejectedExecutionHandler handler    // 拒绝任务时的操作(拒绝策略)
) {}

自定义线程池有两个重要的参数:BlockingQueue 和 RejectedExecutionHandler

  • BlockingQueue 任务队列:

    1. 使用有界队列时,有新的任务需要执行时:

      a. 若线程池实际线程数小于 corePoolSize ,则优先创建线程;

      b. 若大于 corePoolSize 则会将任务加入队列;

      c. 若队列已满,则在总线程数不大于 maximumPoolSize 的前提下,创建新的线程;

      d. 若线程数大于 maximumPoolSize 则执行拒绝策略。

    2. 使用无界队列时,与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来时:

      a. 若线程数小于 corePoolSize 时,则创建新的线程;

      b. 若等于 corePoolSize 则将任务加入队列中,但就不会继续增加线程,也就是说,使用有界队列时,线程池的最大线程数为 corePoolSize;

      c. 若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存为至。

  • RejectedExecutionHandler JDK拒绝策略:

    1. AbortPolicy 该策略直接抛出异常,阻止系统工作

    2. CallerRunsPolicy 只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降。

    3. DiscardOledestPolicy 丢弃最老的一个请求任务,也就是丢弃一个即将被执行的任务,并尝试再次提交当前任务。

    4. DiscardPolicy 默默的丢弃无法处理的任务,不予任何处理。

    5. RejectedExecutioHandler 自定义拒绝策略,接口如下:

      public interfaceRejectedExecutionHandler{
          voidrejectedExecution(Runnable r,ThreadPoolExecutor executor);
      }
      

自定义线程池例子:

public class CustomedThreadPoolTest implements Runnable {
    private int id;
    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public CustomedThreadPoolTest(int id) {
        this.id = id;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            ;
        }
        System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() +
                ":任务-" + this.id);
    }
    
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingQueue queue = new LinkedBlockingQueue();  // (1)
        //ArrayBlockingQueue queue = new ArrayBlockingQueue(5); // (2)
        ThreadPoolExecutor customPool = new ThreadPoolExecutor(
                2,      // 核心线程
                5,      // 最大线程,无界队列时无用
                5,      // 线程最大空闲时间
                TimeUnit.SECONDS,
                queue,  // 任务缓存
                new RejectedExecutionHandler() { // (3)
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("拒绝:任务-" + ((CustomedThreadPoolTest) r).getId());
                    }
                }
        );

        for (int i = 1; i <= 10; i++) {
            customPool.execute(new CustomedThreadPoolTest(i));
        }

        Thread.sleep(1000);
        System.out.println("剩余的任务数量为:" + queue.size());
        customPool.shutdown();
    }
}
  1. 使用无界队列 LinkedBlockingQueue 时,

    • 第一步:创建 corePoolSize=2 个线程分别执行"任务-1"、"任务-2";

    • 第二步:其余任务全部加入到 queue 队列中,若有空闲的线程则依次取出任务执行, 有界队列的最大线程数是 corePoolSize

    剩余的任务数量为:10
    2017-12-09 03:51:32 pool-1-thread-2:任务-2
    2017-12-09 03:51:32 pool-1-thread-1:任务-1
    2017-12-09 03:51:33 pool-1-thread-1:任务-4
    2017-12-09 03:51:33 pool-1-thread-2:任务-3
    2017-12-09 03:51:34 pool-1-thread-1:任务-5
    2017-12-09 03:51:34 pool-1-thread-2:任务-6
    2017-12-09 03:51:35 pool-1-thread-1:任务-7
    2017-12-09 03:51:35 pool-1-thread-2:任务-8
    2017-12-09 03:51:36 pool-1-thread-1:任务-9
    2017-12-09 03:51:36 pool-1-thread-2:任务-10
    2017-12-09 03:51:37 pool-1-thread-1:任务-11
    2017-12-09 03:51:37 pool-1-thread-2:任务-12
    
  2. 使用有界队列 ArrayBlockingQueue 时,

    • 第一步:先创建 corePoolSize=2 个线程执行"任务-1"-"任务-2";

    • 第二步:将其余的任务全部加入到 queue 中,直到 queue 被装满(5个 任务3-任务7);

    • 第三步:创建新的线程执行剩余的任务,直到达到 maximumPoolSize=5 ,即执行"任务8"-"任务10";

    • 第四步:对其余的任务执行拒绝策略,"任务-11"-"任务-12";

    • 第五步:从队列中依次取出线程执行。

    拒绝:任务-11
    拒绝:任务-12
    剩余的任务数量为:5
    2017-12-09 03:46:32 pool-1-thread-2:任务-2
    2017-12-09 03:46:32 pool-1-thread-3:任务-8
    2017-12-09 03:46:32 pool-1-thread-4:任务-9
    2017-12-09 03:46:32 pool-1-thread-5:任务-10
    2017-12-09 03:46:32 pool-1-thread-1:任务-1
    2017-12-09 03:46:33 pool-1-thread-2:任务-3
    2017-12-09 03:46:33 pool-1-thread-3:任务-4
    2017-12-09 03:46:33 pool-1-thread-4:任务-5
    2017-12-09 03:46:33 pool-1-thread-5:任务-6
    2017-12-09 03:46:33 pool-1-thread-1:任务-7
    
  3. 自定义拒绝策略 rejectedExecution

    new RejectedExecutionHandler() { // (3)
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 记录日志,空闲再做处理
            System.out.println("拒绝:任务-" + ((CustomedThreadPoolTest) r).getId());
        }
    }
    

二、ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 是 ExecutorService 的另一个实现类,从上面 Java 线程池 ExecutorService 继承树这幅图可以看出,ScheduledThreadPoolExecutor 直接继承自 ScheduledExecutorService,ScheduledThreadPoolExecutor 类的功能也主要体现在 ScheduledExecutorService 接口上,而所以在介绍 ScheduledThreadPoolExecutor 之前先介绍一下 ScheduledExecutorService 接口。

2.1 ScheduledExecutorService 接口介绍

public interface ScheduledExecutorService extends ExecutorService {

    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
        long initialDelay, long period, TimeUnit unit);

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
        long initialDelay, long delay, TimeUnit unit);
}

从上面接口定义我们知道,提供了四个方法,下面我们就分别介绍:

(1) schedule(Runnable command, long delay, TimeUnit unit)

这个方法的意思是在指定延迟之后运行 task。这个方法有个问题,就是没有办法获知 task 的执行结果。如果我们想获得 task 的执行结果,我们可以传入一个 Callable 的实例。

(2) schedule(Callable callable, long delay, TimeUnit unit)

这个方法与 schedule(Runnable task) 类似,也是在指定延迟之后运行 task,不过它接收的是一个 Callable 实例,此方法会返回一个 ScheduleFuture 对象,通过 ScheduleFuture 我们可以取消一个未执行的 task,也可以获得这个 task 的执行结果。

(3) scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

这个方法的作用是周期性的调度 task 执行。task 第一次执行的延迟根据 initialDelay 参数确定,以后每一次执行都间隔 period 时长。

如果 task 的执行时间大于定义的 period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。

(4) scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

scheduleWithFixedDelay 的参数和 scheduleAtFixedRate 参数完全一致,它们的不同之处在于对 period 调度周期的解释。

在 scheduleAtFixedRate 中,period 指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。

而在 scheduleWithFixedDelay 中,period 指的当前任务的结束执行时间到下个任务的开始执行时间。

2.2 ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,构造参数很简单,只有 3 个:

public ScheduledThreadPoolExecutor(int corePoolSize,
        ThreadFactory threadFactory, RejectedExecutionHandler handler) {

    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}
  1. int corePoolSize:线程池维护线程的最少数量
  2. ThreadFactory threadFactory:线程工程类,线程池用它来制造线程
  3. RejectedExecutionHandler handler:线程池对拒绝任务的处理策略

三、Executors

创建一个什么样的 ExecutorService 的实例(即线程池)需要我们的具体应用场景而定,不过 Java 给我们提供了一个 Executors 工厂类,它可以帮助我们很方便的创建各种类型 ExecutorService 线程池,Executors 一共可以创建下面这四类线程池:

(1) newFixedThreadPool

newFixedThreadPool 创建一个固定数量的线程池,可控制线程最大并发数,超出的线程会在队列中等待。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

(2) newSingleThreadExecutor

newSingleThreadExecutor 创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

(3) newCachedThreadPool

newCachedThreadPool 创建一个可根据实际情况调整线程个数的线程池,不限制最大线程个数。当有任务提交时,用空闲线程执行任务,没有则创建一个线程,并且每个空闲线程会在60秒后自动回收。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

(4) newScheduledThreadPool

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

备注:Executors 只是一个工厂类,它所有的方法返回的都是 ThreadPoolExecutor、ScheduledThreadPoolExecutor 这两个类的实例。


每天用心记录一点点。内容也许不重要,但习惯很重要!

posted on 2018-05-03 08:10  binarylei  阅读(415)  评论(0编辑  收藏  举报

导航