一、概述
java线程池大体由一个线程集合workerSet和一个阻塞队列workQueue组成。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//空闲线程存活时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler)//拒绝策略
1.corePoolSize:核心线程数,已创建的线程<corePoolSize,则新建一个线程执行,>corePoolSize,则放入阻塞队列workQueue中
2.maximumPoolSize:最大线程数,当阻塞队列满了,则会创建新线程,直达maximumPoolSize的值
3.keepAliveTime:当阻塞队列的任务被执行完了,且有空闲线程,使线程个数<=corePoolSize,的时间值
4.unit:keepAliveTime 的单位
5.workQueue:阻塞队列,(1)ArrayBlockingQueue基于数组的有界队列。
(2)LinkedBlockingQueue基于链表的有界队列,但是界限为int的最大值,会一直存放任务,maximumPoolSize无效 。
(3)SynchronousQueue 不缓存任务,放一个执行一个
(4)PriorityBlokingQueue 具有优先级的队列通过comparater实现
6.threadFactory:线程工厂 用老创建线程,指定名字,查日志方便。
7.handler:拒绝策略,(1)Abortpolicy:直接拒绝,抛异常(2)DiscardPolicy:忽略任务,不报错(3)DiscardOlddestPolicy:从队列移除最老的任务,放入新任务 (4)CallerRunsPolicy:如果提交任务失败,会由提交任务的这个线程自己来调用execute执行任务
二、为何生产中一般不用jdk自带的线程池
1.Executors.newFixedThreadPool(int size) 创建固定线程池,用的LinkedBlockingQueue,无限接受任务,导致OOM
2.Executors.newSingleThreadExecutor() 创建一个线程的线程池,用的LinkedBlockingQueue,无限接受任务,导致OOM
3.Executors.newCachedThreadPool() 创建一个带缓存的池,最大无限大,核心线程为0,最大无限大,用的SynchronousQueue 来一个执行一个,任务越多,线程越多
4.Executors.newScheduledThreadPool(int size) 传入的参数为核心线程,最大为无限大,用的DelayedWordQueue 延迟队列
三、源码解析
1.重要属性
/**
* 这个ctl就是用来保存 线程池的状态(runState) 和 线程数(workerCount) 的
* 这里使用AtomicInteger 来保证原子操作
* 这里的ctl的初始值其实就是-1左移29位,即3个1和29个0,
* 111 00000000000000000000000000000
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
// COUNT_BITS值为29,代表着低29位用于存储线程数,高3位用于存储线程池的状态
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池最大的容量,值为3个 0和29个1。也就是536870911
// 000 11111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 下面这5个值代表线程池的状态,存储在高3位中
// 3个1,29个0 111 00000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// 全是0 000 00000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
2.ThreadPoolExecutor.execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();//
if (workerCountOf(c) < corePoolSize) {//当前正在运行的worker数量<corePoolSize
if (addWorker(command, true))//创建一个worker,直接执行任务
return;
c = ctl.get();
}
// isRunning(c)判断线程池是否在运行中,如果线程池被关闭了就不会再接受任务
// workQueue.offer(command) 将任务加入到队列中
if (isRunning(c) && workQueue.offer(command)) {
//如果添加到队列成功了,会再检查一次线程池的状态
int recheck = ctl.get();
//如果线程池关闭了,就将刚才添加的任务从队列中移除
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
// 如果线程是处于RUNNING状态,并且当前线程池中的线程数为0,开启一个新的线程
// 因为有可能任务添加到队列中了,但是却没有线程可执行
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果失败,说明当前线程数已达到maximumPoolSize,需要执行拒绝策略
else if (!addWorker(command, false))//addWorker(command, false)是新开线程执行超过核心线程的任务
reject(command);
}
3.BlockingQueue中的offer(E e)方法
BlockingQueue接口提供了3个添加元素方法:
- add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常;
- offer:添加元素到队列里,添加成功返回true,添加失败返回false;
- put:添加元素到队列里,如果容量满了会阻塞直到容量不满。
已ArrayBlockingQueue为例
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);//入队操作
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
4.addWorker()方法
// 这个方法会创建线程并且执行任务
// 以下几种情况这个方法会返回false:
// 1.传入的core这个参数为true,代表线程数的上限为corePoolSize,
// 如果当前线程数已达到corePoolSize,返回false
// 2.传入的core这个参数为false,代表线程数的上限为maximumPoolSize,
// 如果当前线程数已达到maximumPoolSize,返回false
// 3.线程池stopped或者shutdown
// 4.使用ThreadFactory创建线程失败,或者ThreadFactory返回的线程为null
// 5.或者线程启动出现异常
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 这个rs就是线程池的状态
int rs = runStateOf(c);
// 这里的if说的是以下3种情况,直接返回false,不会创建新的线程:
// 1.rs大于SHUTDOWN,说明线程状态是STOP,TIDYING, 或者TERMINATED,
// 这几种状态下,不接受新的任务,并且会中断正在执行的任务。所以直接返回false
// 2.线程池状态处于SHUTDOWN,并且firstTask!=null。
// 因为SHUTDOWN状态下,是不接收新的任务的。所以返回false。
// 3.线程池处于SHUTDOWN并且firstTask为null,但是workQueue是空的。
// 因为SHUTDOWN虽然不接收新的任务,但是已经进入workQueue的任务还是要执行的,
// 恰巧workQueue中没有任务。所以也是返回false,不需要创建线程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) { // 注意:这里是个for循环
// 获取线程池中线程的数量
int wc = workerCountOf(c);
// 这里传入的core为true代表线程数上限为corePoolSize,
// false代表线程数上限为maximumPoolSize,如果线程数超出上限,直接返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 使用CAS对线程计数+1,如果成功,说明已经满足创建线程的条件了
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果上面的CAS失败,说明有并发,再次获取ctl的值
c = ctl.get(); // Re-read ctl
// 如果线程池的状态发生了变化,例如线程池已经关闭了,
// 导致的CAS失败,那么回到外层的for循环(retry)
// 否则,说明是正常的CAS失败,这个时候进入里面的循环
if (runStateOf(c) != rs)
continue retry;
}
}
// 已经做好创建线程的准备了
// worker是否已经启动的标志位
boolean workerStarted = false;
// 我们前面说了workers这个HashSet用于存储线程池中的所有线程,
// 所以这个变量是代表当前worker是否已经存放到workers这个HashSet中
boolean workerAdded = false;
Worker w = null;
try {
// 传入firstTask这个任务构造一个Worker
w = new Worker(firstTask);
// Worker的构造方法中会使用ThreadFactory创建新的线程,
// 所以这里可以直接获取到对应的线程
final Thread t = w.thread;
// 如果创建线程成功
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 获取线程池的全局锁,下面涉及线程池的操作都需要在持有全局锁的前提下进行
mainLock.lock();
try {
// 获取线程池的状态
int rs = runStateOf(ctl.get());
// 如果rs<SHUTDOWN,说明线程池处于RUNNING状态
// 或者 线程池处于SHUTDOWN状态并且没有新的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果线程已经启动,抛出异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 将包装线程的worker加入到workers这个HashSet中
workers.add(w);
int s = workers.size();
// 我们前面说了,largestPoolSize记录的是线程池中线程数曾经到达的最大值
// 线程池中worker的数量是会变化的,所以记录下worker数的最大值
if (s > largestPoolSize)
largestPoolSize = s;
// 修改标志,代表当前worker已经加入到workers这个HashSet中
workerAdded = true;
}
} finally {
// 释放全局锁
mainLock.unlock();
}
// 如果worker添加成功,启动线程执行任务
if (workerAdded) {
// 启动线程
t.start();
// 代表worker已经启动
workerStarted = true;
}
}
} finally {
// 如果线程没有启动,这里还需要进行一些清理工作
if (! workerStarted)
addWorkerFailed(w);
}
// 返回线程是否成功启动
return workerStarted;
}
// 这个方法做下面几件事:
// 1.将worker从workers中移除
// 2.worker的数量-1
// 3.检查termination
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
// 要操作workers这个HashSet,先获取java线程池全局锁
mainLock.lock();
try {
if (w != null)
// 从worker中移除
workers.remove(w);
// WorkerCount -1
decrementWorkerCount();
// 处理TERMINATED状态
tryTerminate();
} finally {
mainLock.unlock();
}
}
5.启动线程执行任务的操作就是在addWorker中,t.start,调用Worker.run()
public void run() {
// 这里调用的runWorker方法
runWorker(this);
}
// 这里就是执行任务的代码了,有一个while循环不断从队列中取出任务并执行,
// 退出循环的条件是获取不到要执行的任务
final void runWorker(Worker w) {
// 当前线程
Thread wt = Thread.currentThread();
// 前面说了new Worker的时候可以指定firstTask,代表Worker的第一个任务
Runnable task = w.firstTask;
// 这一步就已经将firstTask置为null了
w.firstTask = null;
// 释放Worker的独占锁,这里它释放锁的操作一定会成功,也就是将AQS中state设置为0
w.unlock(); // allow interrupts
// completedAbruptly这个标志位代表当前Worker是否因为执行任务出现异常而停止的
boolean completedAbruptly = true;
try {
// while循环;如果firstTask不为null那就直接执行firstTask,
// 否则就要调用getTask()从队列中获取队列。
// 也就是说Worker的第一个任务是不需要从队列中获取的
while (task != null || (task = getTask()) != null) {
// 给这个worker上独占锁
// Worker加锁的意义在于,在线程池的其他方法中可能会中断Worker,
// 为了保证Worker安全的完成任务,必须要在获取到锁的情况下才能中断Worker,
// 如tryTerminate(),shutdown()等都会关闭worker。
w.lock();
// 如果ctl的值大于等于STOP,说明线程池的状态是STOP,TIDYING或TERMINATED。
// 这个时候需要确保该线程已中断,否则就应该确保线程没有中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
// 其实这里只是设置线程的中断状态
wt.interrupt();
try {
// 这个方法留给子类去实现的
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 真正执行任务的地方
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 后置处理,留给子类去实现的
afterExecute(task, thrown);
}
} finally {
// 最后将task置为null,准备接受下一个任务
task = null;
// 这个worker已完成任务数+1
w.completedTasks++;
// 释放独占锁
w.unlock();
}
}
// 到这一步说明没抛出异常
completedAbruptly = false;
} finally {
// 执行到这里说明:要么队列中已经没有任务了,要么执行任务出现了异常。
// 这个时候需要调用processWorkerExit关闭线程
processWorkerExit(w, completedAbruptly);
}
}
6.从队列中获取任务getTask()
// 这个方法就是从队列中获取任务,返回null代表线程需要被关闭。一共有以下三种可能:
// 1.阻塞获取任务直到获取成功
// 2.获取任务超时了,也就说线程空闲了keepAliveTime这么久了,还是没有获取到任务,
// 这个时候线程需要被关闭(这里有个前提就是线程数要大于corePoolSize)
// 3.如果出现下面几种情况返回null,返回null说明线程需要被关闭
// 池中worker的数量大于maximumPoolSize(由于调用setMaximumPoolSize进行了设置)
// 线程池处于STOP状态,这个时候不能执行任务队列中的任务
// 线程池处于SHUTDOWN状态,但是任务队列是空的
private Runnable getTask() {
boolean timedOut = false; // 最后一次的poll操作是否超时
for (;;) {// for循环
int c = ctl.get();
// 获取线程池的状态
int rs = runStateOf(c);
// 如果线程池的状态大于等于STOP,或者线程池状态等于SHUTDOWN并且任务队列为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 使用CAS对workerCount-1
decrementWorkerCount();
// 返回null
return null;
}
// 获取线程池中的线程数
int wc = workerCountOf(c);
// 如果allowCoreThreadTimeOut设置为true,
// 或者线程池中线程数>corePoolSize,说明有可能发生超时
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果当前线程数大于maximumPoolSize,或者超时
// 注意:如果开发者调用了setMaximumPoolSize() 将maximumPoolSize变小了,
// 就有可能出现当前线程数大于maximumPoolSize。
// 这个时候多余的线程肯定是获取不到任务的,需要被关闭
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// workerCount-1
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 到这里,说明线程数小于maximumPoolSize等于且没有超时
try {
// 从任务队列中取出任务
// 如果timed为true,调用带超时的poll方法,否则执行take方法阻塞获取任务。
// timed这个变量的值取决于allowCoreThreadTimeOut || wc > corePoolSize
// 其实这里说的是,如果线程池中线程数量在corePoolSize以内,
// 且不支持回收核心线程数内的线程,这个时候线程池中的线程是不会被回收的。
// 所以调用take方法阻塞获取任务,直到获取成功。
// 否则的话,线程隔了keepAliveTime这么久还是获取不到任务,是需要被回收的
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果成功获取到任务,返回这个runnable任务,
// 否则就是超时了,再进入下一轮循环的时候返回null
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
7.阻塞队列中的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 或者 workQueue.take();方法,移除队列头部元素,通过ReentrantLock 加锁await(),signal()来阻塞唤醒线程
BlockingQueue接口提供了3个删除方法:
- poll:删除队列头部元素,如果队列为空,返回null。否则返回元素;
- remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false;
- take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除。
以ArrayBlockingQueue为例
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)//队列中没有元素时调用condition.await阻塞
notEmpty.await();
return dequeue();//出队操作
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);//队列中没有元素时,或者超时,调用condition.await阻塞
}
return dequeue();//出队操作
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
四、总结
线程池内部工作原理:
- 首先,调用execute()执行方法时,先判断线程池中工作线程的数量小于corePoolSize,则调用addWorker()方法,在addWorker()方法里创建新线程(判断当前线程池的状态,shundown,stop等不执行,返回false)
- 创建线程成功,ReentrantLock加锁,放入HashSet 中 解锁
t.start()执行线程。如果大于corePoolSize,则调用workQueue.offer(command)放入阻塞队列中,阻塞队列采用ReentrantLock加锁保证线程安全 - 如果阻塞队列满了,调用addWorker(command, false)方法,新建线程加入HashSet
中,如果>maximumPoolSize !addWorker(command, false)
true调用reject(command) 执行拒绝策略方法。 - addWorker()中线程创建好了,t.start()执行任务 就是调用runWorker(Worker w)方法,Runnable
task = w.firstTask;第一个任务直接执行,后续的调用getTask()从队列中for(;;)调用
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)或者
workQueue.take()从阻塞队列头部获取任务,方法内部ReentrantLock加锁保证线程安全
五、问题
-
线程执行任务过程中出现异常是怎么处理的?
如果一个线程执行任务出现异常,那么执行任务的线程会被关闭,而不会继续执行其他任务。最后会启动一个新的线程来取代它
-
线程池是怎么实现线程复用的?
runWorker()方法中,一个线程执行完一个任务后会不断从任务队列中取出任务来执行,如果队列中已经没有任务了,allowCoreThreadTimeOut设置为false并且线程数<=corePoolSize,调用BlokingQueue.take()方法阻塞,直到获取到任务
如果队列中没有任务了,allowCoreThreadTimeOut设置为true或者线程数>corePoolSize,调用BlockingQueue带超时的poll方法尝试获取任务,获取不到的话,这个线程就会被回收掉 -
shutdown() 和 shutdownNow()有什么区别?
线程在拿到任务的时候开始执行的时候,是会获取Worker的独占锁的。shutdown()方法中断worker会先调用Worker.tryLock()获取独占锁,如果线程正在执行任务,那就获取不到独占锁,也就无法中断线程。而shutdownNow()方法是直接尝试中断所有线程,它们底层都是调用Thread.interrupt()方法给线程设置interrupt标记,所以只有响应中断的任务在interrupt()以后才会终止
1.当创建线程池后,初始时,线程池处于RUNNING状态,此时线程池中的任务为0;
2.如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
3.如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
4.当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。接着会执行terminated()函数。
5.线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED,线程池被设置为TERMINATED状态。