使用线程池的优点:
- 重用已经创建好的线程,避免频繁创建和销毁的系统开销
- 控制线程并发数,合理使用系统资源,提高应用的性能
- 手动管理线程,比如定时执行、取消执行等
Executors线程池工厂类
Excutors是个工厂类,里面提供多了创建多种不同类型池的方法,常用的有 :
- FixedThreadPool: 指定核心线程数目的一个线程池,缓冲池无限大
- CacheThreadPool:无核心线程,非核心线程可创建N个。
- SingleThreadPool: 创建只有一个核心线程的线程池,缓冲池无限大
- ScheduleThreadPool: 创建一个可以延迟且定期执行任务的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ThreadPoolExecutor线程池具体实现类
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//参数判断,不满足条件抛exception
//corePoolSize需大于等于0,maximumPoolSize需大于0,且大于等于corePoolSize,keepAliveTime需大于等于0
//workQueue 、threadFactory 、handler 不能为null
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize 核心线程数大小: 提交一个任务时,如果当前线程数小于corePoolSize,就会创建一个线程(即使其他有可用的空闲线程)。默认情况下,核心线程会一直存活(即使处于空闲状态)。如果ThreadPoolExecutor的 allowCoreThreadTimeout设置为true,则核心线程也会存在超时策略,超时时长由keepAliveTime决定,当等待时间超过keepAliveTime,核心线程就会终止。
blockingQueue< Runnable> 阻塞队列: 用于存储等待执行的任务列表。不同的blockingQueue< Runnable>对线程池运行逻辑有很大影响,可以选择以下几个阻塞队列:
- ArrayBlockingQueue:基于数组的有界阻塞队列, FIFO(先进先出)原则,创建时必须指定大小;
- LinkedBlockingQueue:基于链表的阻塞队列,FIFO (先进先出)原则。
如果创建时没有指定容量,则默认为Integer.MAX_VALUE(无限);
- SynchronousQueue:一个不存储元素的阻塞队列,相当于缓冲区只有1个位置
每个插入操作必须等上一个元素被移除之后,否则插入操作一直处于阻塞状态
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
maximumPoolSize 线程池最大容量: 线程池允许创建的最大线程数。
keepAliveTime 保活时间: 线程执行结束后,保持存活的时间。
ThreadFactory 线程工厂: 统一创建管理
RejectedExecutionHandler: 线程池队列饱和之后的执行策略,默认是采用AbortPolicy。JDK提供四种实现方式:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- CallerRunsPolicy :由调用线程处理该任务
- DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- DiscardPolicy : 丢弃任务,但是不抛出异常
TimeUnit: keepalive的时间单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
线程池状态
成员变量ctl,AtomicInteger这个类可以通过CAS达到无锁并发,效率比较高,这个变量有双重身份,它的高三位表示线程池的状态,低29位表示线程池中现有的线程数,这也是Doug Lea一个天才的设计,用最少的变量来减少锁竞争,提高并发效率。
线程池的状态,有5种,
- RUNNING, 运行状态,值也是最小的,刚创建的线程池就是此状态
- SHUTDOWN,停工状态,不再接收新任务,已经接收的会继续执行
- STOP,停止状态,不再接收新任务,已经接收正在执行的,也会中断
- TIDYING,清空状态,所有任务都停止了,工作的线程也全部结束了
- TERMINATED,终止状态,线程池已销毁
// //CAS,无锁并发
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//表示线程池线程数的bit数,29位
private static final int COUNT_BITS = Integer.SIZE - 3;
//最大的线程数量,低29位都是1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//获取线程池的状态,高3位表示
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程的数量,低29位表示
private static int workerCountOf(int c) { return c & CAPACITY; }
//组装状态和数量,成为ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
-
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
-
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
-
shutdown方法会将正在执行的任务继续执行完,而shutdownNow会直接中断正在执行的任务。调用了这两个方法的任意一个,isShutdown方法都会返回true,当所有的线程都关闭成功,才表示线程池成功关闭,这时调用isTerminated方法才会返回true。
提交任务
execute/submit
向线程池提交任务有这2种方式,execute是ExecutorService接口定义的,submit有三种方法重载都在AbstractExecutorService中定义,都是将要执行的任务包装为FutureTask来提交,使用者可以通过FutureTask来拿到任务的执行状态和执行最终的结果,最终调用的都是execute方法,其实对于线程池来说,它并不关心你是哪种方式提交的,因为任务的状态是由FutureTask自己维护的,对线程池透明。
execute(runnable)提交任务
-
如果目前运行线程数少于核心线程数,则addWorker(command, true)创建核心线程并启动
-
判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否在运行状态
1、如果非运行状态,移除插入的任务,然后执行拒绝策略。
2、如果在运行状态,或者非运行状态但移除失败
3、再次判断,如果当前线程总数为0,则添加新线程(但不指定firstTask)并执行
- 如果线程池非运行状态或者缓存队列已满,addWorker(command, false) 尝试新建非核心线程,进行处理。如果添加非核心线程失败,执行拒绝策略。
线程没有核心非核心之分,只是在addWorker里判断(允许创建最大线程数)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//检查当前线程数是否达到核心线程数的限制,注意线程本身是不区分核心还是非核心
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return; //如果线程总数小于核心线程总数,则直接创建线程,执行任务
//如果添加任务失败,刷新ctl,进入下一步
c = ctl.get();
}
//检查线程池是否是运行状态,然后将任务添加到等待队列,注意offer是不会阻塞的
if (isRunning(c) && workQueue.offer(command)) {
//任务成功添加到等待队列,再次刷新ctl
int recheck = ctl.get();
//如果线程池不是运行状态,则将刚添加的任务从队列移除并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//判断当前线程数量,如果线程数量为0,则添加一个非核心线程,并且不指定首次执行任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果线程池非运行状态,或者缓冲池已满
//添加非核心线程,指定首次执行任务,如果添加失败,执行异常策略
else if (!addWorker(command, false))
reject(command);
}
添加worker
- execute方法虽然没有加锁,但是在addWorker方法内部,加锁了,这样可以保证不会创建超过我们预期的线程数,在最小的范围内加锁,尽量减少锁竞争
- core参数,只是用来判断当前线程数是否超量的时候跟corePoolSize还是maxPoolSize比较,Worker本身无核心或者非核心的概念。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果线程池的状态到了SHUTDOWN或者之上的状态时候,只有一种情况还需要继续添加线程,
//那就是线程池已经SHUTDOWN,但是队列中还有任务在排队,而且不接受新任务(所以firstTask必须为null)
//这里还继续添加线程的初衷是,加快执行等待队列中的任务,尽快让线程池关闭
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//传入的core的参数,如果线程数超过理论最大容量,如果core是true跟最大核心线程数比较,否则跟最大线程数比较
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//通过CAS自旋,增加线程数+1,增加成功跳出双层循环,继续往下执行
if (compareAndIncrementWorkerCount(c))
break retry;
//检测当前线程状态如果发生了变化,则继续回到retry,重新开始循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//走到这里,说明我们已经成功的将线程数+1了,但是真正的线程还没有被添加
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); //添加线程,Worker是继承了AQS,实现了Runnable接口的包装类
final Thread t = w.thread; //得到thread
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); //到这里开始加锁
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
//检查线程状态,只有当线程池处于RUNNING
//或者处于SHUTDOWN并且firstTask==null的时候,这时候创建Worker来加速处理队列中的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) //线程只能被start一次
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //启动Worker
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
启动任务
//Worker的run方法调用的是ThreadPoolExecutor的runWorker方法
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//取出需要执行的任务,
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果task不是null,或者去队列中取任务,注意这里会阻塞,后面会分析getTask方法
while (task != null || (task = getTask()) != null) {
w.lock(); //如果线程被中断,那么会抛出InterruptedException,而退出循环,结束线程
//判断线程是否需要中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//任务开始执行前的hook方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); //执行这个新创建的task,或者是从workQueue中取出一个任务,进程执行
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//任务开始执行后的hook方法
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//Worker退出
processWorkerExit(w, completedAbruptly);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池的状态,如果已经是STOP及以上的状态,或者已经SHUTDOWN,队列也是空的时候
//直接return null,并将Worker数量-1
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//是否存在超时策略
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
//如果存在超时策略(非核心线程,核心线程且allowCoreThreadTimeOut = true),阻塞指定时间后结束线程
//如果不存在超时策略(核心线程,且allowCoreThreadTimeOut = false),则无限等待,一直阻塞
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take(); //一直阻塞
if (r != null)
return r; // 在保活时间内,如果workQueue中有新的任务到来,复用此线程,减少任务的创建和销毁开销。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
线程池的线程是如何做到复用的
线程池中的线程在循环中尝试取任务执行,这一步会被阻塞,如果设置了allowCoreThreadTimeOut为true,则线程池中的所有线程都会在keepAliveTime时间超时后还未取到任务而退出。或者线程池已经STOP,那么所有线程都会被中断,然后退出。
线程池是如何做到高效并发的
看整个线程池的工作流程,有以下几个需要特别关注的并发点.
- 线程池状态和工作线程数量的变更。这个由一个AtomicInteger变量 ctl来解决原子性问题。
- 向工作Worker容器workers中添加新的Worker的时候。这个线程池本身已经加锁了。
- 工作线程Worker从等待队列中取任务的时候。这个由工作队列本身来保证线程安全,比如LinkedBlockingQueue等。