Java线程池
一、线程池的实现
如果我们每次使用一个线程就去创建一个线程,使用起来非常方便,但是频繁创建线程会大大降低系统效率,因为创建和销毁线程都需要时间。在java中,我们可以通过线程池来实现线程的复用。ThreadPoolExecutor是线程池中最核心的一个类。
// 构造方法1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 构造方法2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 构造方法3
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// 构造方法4
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor一共有4个构造方法,但是从代码可以看出来,前三个构造方法都是调用最后一个构造方法进行的初始化工作。
1.1、各个参数的含义
corePoolSize:核心池的大小。
在创建了线程池之后,默认情况下,线程池中并没有任何线程,而是等待有任务来了才去创建线程。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,这两个方法是预先创建线程的,即在没有任务来临之前,就先创建了corePoolSize个线程或者一个线程。但是默认情况下,线程池的线程数为0,当有任务到来时,才创建线程执行任务,当线程池中的数量达到了corePoolSize的时候,就把接下来的线程放入缓存队列。
maximumPoolSize:线程池最大线程数。它表示线程池中最多能创建多少线程。
keepAliveTime:表示线程没有任务执行时最多保持多长时间会终止。默认情况下,只有线程池的线程数量大于corePoolSize的时候,keepAliveTime才会起作用,如果一个线程的空闲时间达到了keepAliveTime,则会终止,直到线程数不大于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,就算线程池中的线程数不大于corePoolSize,keepAliveTime参数也会起作用。
unit:keepAliveTime的时间单位。有7种取值。
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务。一般来说,这个参数有以下几种选择。
- ArrayBlockingQueue:是一个基于数组的有序阻塞队列,此队列按照FIFO(先进先出)原则对元素进行排列。
- LinkedBlockingQueue:一个基于链表的有序阻塞队列,此队列按照FIFO(先进先出)排序,吞吐量通常要高于ArrayBlockingQueue。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量要高于LinkedBlockingQueue。
threadFactory:线程工厂。主要用来创建线程
handler:表示拒绝处理任务时的策略,当运行线程数已经到达maximumPoolSize时,队列也已经装满时,会调用该参数值拒绝任务。默认情况是AbortPolicy。
有以下四种取值:
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来执行任务。
DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
1.2、ThreadPoolExecutor的继承关系
public class ThreadPoolExecutor extends AbstractExecutorService { … }
public abstract class AbstractExecutorService implements ExecutorService { … }
public interface ExecutorService extends Executor { … }
public interface Executor { … }
1.3、ThreadPoolExecutor的状态变量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
其中ctl是ThreadPoolExecutor的状态变量。
workerCountOf():方法取得当前线程池的线程数量,算法是将ctl的值取低29位。
runStateOf():方法取得线程池的状态,算法是将ctl的值取高3位。
RUNNING 111:表示正在运行
SHUTDOWN 000:表示拒绝接收新的任务。
STOP 001:表示拒绝接收新的任务,并且不再处理任务队列中剩余的任务,还要中断正在执行的任务
TIDYING 010:表示所有线程已经停止,准备执行terminated()方法。
TERMINATED 011:表示已经执行完terminated()方法。
1.4、任务的执行:execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
// 第一种情况
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 第二种情况
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 第三种情况
else if (!addWorker(command, false))
reject(command);
}
以上三种情况分别对应以下:
- 1.线程池的线程数量小于corePoolSize核心线程任务数量,开启核心线程执行任务。
- 2.线程池的线程数量不小于corePoolSize核心线程数量,或者开启核心线程失败,尝试将线程以非阻塞的方式添加到任务队列。
- 3.任务队列已满导致添加任务失败,开启新的非核心线程任务。
二、常用的四种线程池
2.1、newFixedThreadPool
一个固定线程数量的线程池。可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
// corePoolSize和maximumPoolSize大小一样,同时传入一个无界阻塞队列,
// 该线程池的线程数会维持在指定线程数,不会进行回收(根据worker逻辑分析)
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.2、newCachedThreadPool
不固定线程数量,且支持最大为Integer.MAX_VALUE的线程数量。
public static ExecutorService newCachedThreadPool() {
// corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE
// 就是说来一个任务就创建一个worker,回收时间是60s
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可缓存线程池:
- 1.线程数无限制
- 2.有空闲线程则复用空闲线程,没有就创建新线程
- 3.一定程度减少频繁创建/销毁线程,减少系统开销
2.3、newSingleThreadExecutor
可以理解为线程数为1的FixedThreadPool。
public static ExecutorService newSingleThreadExecutor() {
// corePoolSize和maximumPoolSize大小都为1,
// 线程池中只有一个线程在运行,其他的都放入阻塞队列。
// 外面包装的FinalizableDelegatedExecutorService类实现了finalize方法,
// 在JVM垃圾回收的时候会关闭线程池。
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
单线程化的线程池:
- 1.有且只有一个工作线程执行任务
- 2.所有任务按照指定顺序执行,遵循队列的先入先出
2.4、newScheduledThreadPool
支持定时以指定周期循环执行任务。前三种线程池是ThreadPoolExecutor不同配置的实例,最后一种是ScheduledThreadPoolExecutor的实例。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
三、线程池的线程是怎么创建的?是一开始就随着线程池的启动创建的吗?
不是的。线程池默认初始化之后不启动Worker,等待有请求时才启动。
每当我们调用excute()方法添加一个任务时,线程池会做以下判断。
- 1、首先判断核心线程池是否已满。如果正在运行的线程个数小鱼核心线程池的数量,那么直接创建线程运行这个任务。
- 2、如果核心线程池已满,那么将这个任务放入队列。
- 3、如果队列满了,而且正在运行的线程数量小于maximum pool size(最大线程数),那么就创建非核心线程立刻运行这个任务。
- 4、如果队列满了,而且正在运行的线程数量大于或等于maximum pool size(最大线程数),那么线程池就会抛出异常Reject Execution Exception。当一个线程完成任务时,会从一个队列中取下一个任务来执行。如果一个线程无事可做,超过一定得时间会停掉这个线程。
四、如何向Java线程池中提交线程/任务?
线程池中常用的提交任务/线程的方法有两种:
4.1 execute()
execute()方法接收一个Runnable类型的参数,无返回值。(上面1.4已经分析过了)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
4.2 submit()方法
submit方法有返回值。线程会返回一个Future类型的对象,通过这个Future可以判断任务是否执行成功,并且可以通过Future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)则会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有执行完。
future的get方法未获得返回值之前会一直阻塞,可以使用future的isDone方法判断任务是否执行完成,然后再决定是否get。