线程池好处

  • 重用存在的线程,减少对象创建,销毁的开销
  • 可有效控制最大并发线程数
  • 提供定时执行,定期执行等功能.

ThreadPoolExecutor

线程池可以通过ThreadPoolExecutor生成,以下是它的构造参数:

public TheadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler) 

几个核心参数的作用:

  • corePoolSize: 核心线程数量
  • maximumPoolSize: 最大线程数
  • workQueue: 任务队列,存储等待执行的任务(有界阻塞)
  • keepAliveTime: 线程没有任务执行时最多保持多久时间
  • unit: 上面的时间单位
  • threadFactory: 线程工厂,用于创建线程
  • rejectHandler: 当队列已满时如何处理任务的策略

任务执行

线程池执行过程,亦即execute()方法的调用过程:

img

  • 提交一个任务,若线程池里的线程数小于corePoolSize时,创建一个新线程去处理提交任务.
  • 如果线程数已经大于等于corePoolSize,新提交的任务会被放入任务队列workQueue排队等待执行.
  • 如果线程数已经大于等于corePoolSize,并且任务队列也已经满了,会判断线程数是否达到maximumPoolSize,若未达到,则创建一个非核心线程来执行提交的任务.
  • 如果线程数已经达到maximumPoolSize,新任务到来之时会采用拒绝策略处理.

四种拒绝策略

  • AbortPolicy(默认,抛出一个异常)
  • DiscardPolicy(直接丢弃新任务)
  • DiscardOldestPolicy(丢弃队列里最老的任务,将当前任务继续提交给线程池)
  • CallerRunsPolicy(交给线程池调用所在的线程进行处理,即此任务会从异步执行变为同步执行)

线程池异常处理

  • 异常需要主动在任务中用try catch捕获,不会抛出到主线程.

  • 如果调用的是submit()方法,异常也可以在FutureTask的get()方法处抛出.

  • 也可以为工作线程设置UncaughtExceptionHandler来处理异常.

ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(
                    (t1, e) -> {
                        System.out.println(t1.getName() + "线程抛出的异常"+e);
                    });
            return t;
           });
        threadPool.execute(()->{
            Object object = null;
            System.out.print("result## " + object.toString());
        });


工作队列

种类有:

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • DelayQueue
  • PriorityBlockingQueue
  • SynchronousQueue

ArrayBlockingQueue: 用数组实现的有界阻塞队列,FIFO

LinkedBlockingQueue: 用链表实现的阻塞队列,FIFO,容量可以进行设置,若不设置的话默认无边界.newFixedThreadPool,newSingleThreadExecutor线程池使用了这个队列

DelayedWorkQueue: 一个任务定期执行的延迟队列,根据指定的执行时间从小到大排序,否则根据入队的先后排序.newScheduledThreadPool线程池使用了这个队列

PriorityBlockingQueue: 具有优先级的无界阻塞队列

SynchronousQueue: 一个不存储元素的阻塞队列,只负责移交数据,每个插入操作必须等待另一个线程取出,否则一直处于阻塞状态.newCachedThreadPool线程池使用了这个队列。


Executors创建的几种常用的线程池

  • newFixedThreadPool (固定数目线程的线程池) :

  • newCachedThreadPool(线程无限的线程池)

  • newSingleThreadExecutor(单线程的线程池)

  • newScheduledThreadPool(定时或周期执行的线程池)

newFixedThreadPool特点 :

  • 核心线程数和最大线程数大小一样(数量由用户指定,必须)
  • 没有所谓的非空闲时间,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue
  • fixed的意思即队列大小适应任务数量,来多少处理多少
  • 不会丢弃任务,但是任务积压过多会导致内存溢出.
  • 适用于CPU密集任务,不适用IO密集任务

newCachedThreadPool特点:

  • 核心线程数为0,即所有线程都是非核心线程
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程空闲存活为60s
  • 适用并发执行大量短期的小任务

newSingleThreadExecutor特点:

  • 核心线程和最大线程都是1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0
  • 适用于串行执行任务
  • 串行任务的数量一般不是很大,因此不用担心内存爆炸的问题

newScheduledThreadPool特点:

  • 最大线程数是Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • 可选延迟策略有两种:
    • scheduleAtFixedRate() :按某种速率周期执行
    • scheduleWithFixedDelay():在某个延迟后执行

线程池状态

image

  • running: 运行态
  • shutdown: 调用shutdown()方法后进入此阶段,不再处理新任务,任务队列的任务处理完后会进入tidying
  • stop: 调用shutdownNow()方法后进入此阶段,新任务与任务队列的任务都不处理,当各线程完成后进入tidying
  • tidying: 该状态表明所有任务都运行完毕,可以准备进行销毁
  • terminated: tidying阶段调用terminated()后进入此阶段(该方法什么也不做),表示线程池彻底终止.

一些思考题

  1. 为什么newCachedThreadPool要使用SynchronousQueue队列?

    原因有两个:

    • SynchronousQueue结构简单,消费速度比较快,高并发下效率更高些.
    • SynchronousQueue适用于生产与消费速率一致的情况,而当队列中有新任务之后,newCachedThreadPool能马上新建线程去消费任务,因此能发挥SynchronousQueue的高性能优势