文章目录
线程池的作用
线程池的作用是为了复用线程,便于线程的管理及规范化。
线程池复用原理
每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run方法。 那么该类的 run()方法中就是调用了Runnable对象的run()方法。我们可以继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的Runnable对象。这就是线程池的实现原理。循环方法中不断获取 Runnable是用Queue实现的,在获取下一个 Runnable 之前可以是阻塞的。
线程池运行过程
当提交一个新线程时,判断当前运行线程数是否小于核心线程,如果小于则直接创建,否则判断阻塞队列是否满,如果未满则加入阻塞队列,否则判断当前运行线程是否小于最大线程数量,如果小于则创建线程运行,否则调用拒绝策略。
Java提供的线程池
四种常用线程池
- FixedThreadPool
使用Executors.newFixedThreadPool(int nThreads)方法可以创建一个固定线程数量的线程池。
创建一个线程池,该线程池重用固定数量的线程。在任何时候,最多有nThreads个线程将是活动的处理任务。超出则进入阻塞队列等待。public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
- SingleThreadExecutor
使用Executors.newSingleThreadExecutor()方法可以创建一个固定线程数量为1的线程池。public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
- CachedThreadPool
使用Executors.newCachedThreadPool()方法可以创建一个简单的可缓存的线程池。
创建一个线程池,当有一个新线程提交时,查看是否能复用旧线程,否则创建一个新线程并加入到池中,如果空闲线程在60秒内无活动则移出线程池。public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
- ScheduledThreadPoolExecutor
使用Executors.newSingleThreadScheduledExecutor()方法可以创建一个可定时任务的线程池。
创建一个线程池,新线程可设置在提交后多久进行触发。
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,所以使用super调用父类构造方法创建线程.public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService { public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); } }
四种线程池存在问题:
FixedThreadPool 和 SingleThreadPool :允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
CachedThreadPool 和 ScheduledThreadPool :允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM
自定义线程池ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
构造方法个属性解析
1. corePoolSize:线程池中的核心线程数量,即使他们是空闲状态。allowCoreThreadTimeOut设置为true后,核心线程空闲时也可通过keepAliveTime超时移除。
2. maximumPoolSize:线程池中最大允许的线程数量。
3. keepAliveTime:当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
4. unit:keepAliveTime的时间单位
5. workQuene:任务队列,被提交但尚未被执行的任务。
6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。
7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
BlockingQueue阻塞队列
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
默认构造方法容量为Integer.MAX_VALUE,不断向队列添加任务就可能会导致内存溢出。 - PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
ThreadFactory线程工厂
作用是规范某个线程池内容,如线程名。
默认线程工厂如下:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
RejectedExecutionHandler拒绝策略
当等待队列无法放入新线程时触发拒绝策略。
- AbortPolicy: 直接抛出RejectedExecutionException异常。
- DiscardPolicy:什么都不做。
- DiscardOldestPolicy:丢弃等待队列中最早的一个线程,并再次执行当前提交线程。
- CallerRunsPolicy:直接由调用者线程自己执行这个线程。
线程池源码分析
功能方法
阅读上述线程池分析后,想必你对于内部逻辑有一些好奇,下面就贴一些针对于基础属性进行分析。
// 线程池状态参数: 前3位表示线程池运行状态,后29位表示线程池中线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 32-3=29 表示使用29表示线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// 值为 000 11111111111111111111111111111
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
//表示线程池的几种状态
// RUNNING: 111 00000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// SHUTDOWN: 000 00000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// STOP: 001 00000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// TIDYING: 010 00000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// TERMINATED: 011 00000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 通过以下3个方法分析出ctl的使用流程
// 该方法返回值为当前线程池状态 c & 111 00000000000000000000000000000
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
// 该方法返回值为当前线程数量 c & 000 11111111111111111111111111111
private static int workerCountOf(int c) { return c & COUNT_MASK; }
// 该方法返回值为ctl。将线程池状态和线程数量填入合并为ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 以下3个方法为CAS和普通修改线程池数量
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
private void decrementWorkerCount() {
ctl.addAndGet(-1);
}
// 控制访问工作线程的集合和其他监控属性
private final ReentrantLock mainLock = new ReentrantLock();
// 包含池中所有工作线程的集合。通过mainLock来保证并发安全
private final HashSet<Worker> workers = new HashSet<>();
private volatile long keepAliveTime;
// 我们先前了解到keepAliveTime属性的含义,如下布尔值为控制核心线程数的消亡,如果为true则核心线程也会根据keepAliveTime值销毁。
private volatile boolean allowCoreThreadTimeOut;
线程池状态描述及流转如下:
- RUNNING: 接受新的任务,处理等待队列中的任务
- SHUTDOWN: 不接受新的任务提交,但是会继续处理等待队列中的任务
- STOP: 不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程
- TIDYING: 所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()
- TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个
RUNNING -> SHUTDOWN : 调用shutdown()方法会从运行态转为中止状态。
(RUNNING or SHUTDOWN) -> STOP : 调用shutdownNow()方法会从运行或中止态转为终止状态。
SHUTDOWN -> TIDYING: 当线程池和阻塞队列为空时变化
STOP -> TIDYING: 当线程池为空时变化
TIDYING -> TERMINATED: 当terminated()方法执行完成
提交任务
上面简单分析了一些重要参数,接下来我们先分析核心提交流程,看是否跟我们上述所说一致,线程池提交任务方法为execute();
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 1. 获取当前状态,并调用workerCountOf方法获取线程池数量判断是否小于核心线程,如果小于则创建Worder,否则执行下一步骤
// 2. 判断当前是否是运行状态且入队成功,如果成功,则重复检查下运行状态,如果状态被并发改变时,移除队列中新添加的任务,执行拒绝策略,
// 如果还在运行状态,则检查是否还有工作线程,检查目的是防止无工作线程且阻塞队列中有任务无线程执行。如果无工作线程则创建临时线程执行。
// 3. 如果线程池状态不满足或入阻塞队列失败,则创建临时线程执行,如果创建失败则执行拒绝策略。
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);
}
addWorder方法分析如下,该方法的返回值为工作线程是否启动,firstTask参数为工作线程启动后第一个执行的任务,如果无任务则为null,默认从阻塞队列中取任务,core参数为该工作线程是否是核心线程,核心线程和非核心线程的区别是默认非核心线程超过空闲时间会被消除,而核心线程不会,具体区别上文已讲述清楚。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// 检查线程池运行状态,runStateAtLeast方法是比较方法,参数一大于等于参数二
// 当当前线程池状态大于等于SHUTDOWN时,可以是除了RUNNING的其他4种状态,详情看上文中状态的具体值
// 这里主要是SHUTDOWN状态和STOP状态区别,STOP不处理任务,SHUTDOWN处理任务。
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
// CAS+自旋 自增线程个数,当发生竞争失败或者数量限制返回false
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
// 该类为线程池内部类,稍后我们进行分析
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 对工作线程数组的修改需要持有mainLock
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.isAlive()) // 重复运行
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 添加成功后工作线程启动,没成功可能是因为线程池状态不符合或线程已被启动
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果启动失败则从工作队列中移除,工作线程数自减,判断是否需要进入Terminate状态
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
通过以上两个方法源码的深入了解,我们大概了解了线程池的工作流程,就是根据线程池的运行状态和工作线程数进行判断线程池是否需要创建新线程或者接受新任务。
工作线程
接下来我们通过分析内部类Worker来具体了解线程池中工作线程的工作原理。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
// 调用runWorker方法前禁止中断
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
可以看到Worker类继承AQS,实现了一个非公平的不可重入的锁,为了解决工作线程运行中和非运行时的一个状态区分,保证了SHUTDOWN和STOP的区别。并且实现了Runnable接口重写了run方法,runWorker方法则是具体的工作流程方法。
构造方法中通过firstTask属性来保证执行第一个传入的任务,通过thread属性来保证每个线程都是由自定义的ThreadFactory返回的。
接下来我们查看runWorker方法详细分析工作流程
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不为空则直接执行 否则从阻塞队列中获取执行
while (task != null || (task = getTask()) != null) {
w.lock();
// 保证中断标志不丢失,重复检测为了避免shutdownNow的竞争
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
// 提交的Runnable类作为一个普通方法执行
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 从工作线程集合中移除,并自减线程数(如果是正常退出)
// 判断是否需要临时增加非核心线程进行临时处理
// 调用tryTerminate方法
processWorkerExit(w, completedAbruptly);
}
}
我们通过addWorker方法了解到,每次提交一个任务后,任务作为firstTask属性传入,并进行start启动该Worker线程,我们每次提交的任务作为一个普通方法被Worker线程执行。
通过addWorker方法我们可以很方便的理解runWorker方法的执行流程,该方法是写在Worker的run方法中,当线程被启动后执行,正常流程为先获取执行任务(这个任务可以是firstTask属性或者从阻塞队列中取到的任务),获取锁,执行钩子函数,执行任务run方法,执行钩子函数,最后解锁并记录监控数据(当前线程执行多少个任务)。
Worker执行任务前加锁的目的是为了保证shutdown和shutdownNow两种不同中断方式的区别,下文详解。
中断线程
两种方式可以中断线程池,一种是调用shutdown方法,另一种是调用shutdownNow方法。
- shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 对于workers集合的修改需要持有锁
mainLock.lock();
try {
// 检查安全性------ 对于主流程无影响
checkShutdownAccess();
// 当前状态如果小于SHUTDOWN则CAS加自旋变为SHUTDOWN,即如果当前状态为RUNNING设置为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断线程如果线程未在执行任务
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
- shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置线程状态为STOP
advanceRunState(STOP);
// 中断线程
interruptWorkers();
// 阻塞队列中未执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
上述两个函数都表示中断线程池,区别在于中断线程使用的方法不同,并且shutdownNow返回阻塞队列中未执行的线程,而shutdown会将阻塞队列中的任务执行完成。
分析interruptIdleWorkers方法和interruptWorkers方法的不同。
- interruptIdleWorkers
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 遍历每个工作线程,通过tryLock来获取锁,区分空闲线程和正在工作的线程,空闲线程则直接设置中断位,正在工作的线程则通过其他方式来保证
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
- interruptWorkers
private void interruptWorkers() {
// assert mainLock.isHeldByCurrentThread();
for (Worker w : workers)
// 如果线程启动则直接中断
w.interruptIfStarted();
}
通过分析interruptIdleWorkers方法和interruptWorkers方法的不同。可以理解SHUTDOWN状态和STOP状态的不同。
SHUTDOWN状态和STOP状态流入TIDYING和TERMINATED是通过tryTerminate方法执行的。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 如果是RUNNING或TIDYING或TERMINATED状态则直接返回,如果是SHUTDOWN状态且队列不为空直接返回
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
return;
// 判断到这里有两种情况,一种是STOP状态,一种是SHUTDOWN状态且阻塞队列为空
// 当工作线程为0时转移状态,否则中断线程。
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 钩子函数
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
该方法的调用时机为两个中断线程池方法,添加工作线程失败,工作线程退出,工作线程集合删除线程时会调用,通过最开始的判断保证不会进行意外的状态变化。
总结
通过上述源码分析我们了解了线程池的工作流程,通过execute提交新任务,线程池根据运行状态启动Worker线程执行提交来的任务,通过shutdown和shutdownNow方法来进行关闭线程池。
聊一下线程池中使用的两个锁
一个是mainLock,用作对于工作线程集合的安全访问和一些其他监控参数(largestPoolSize,completedTaskCount)的安全读取。
另一个是Worker类自己实现的简单不可重入锁,用于区分工作线程执行时机和从阻塞队列获取任务的时机,用于两种停止的不同响应。
聊一下线程池中关于中断的逻辑
无论是线程池中的工作线程中断还是AQS中的获取锁时发生的中断,这些框架仅仅保证中断标志位不会丢失,中断的处理还是交由用户线程去保证,如果用户线程并未保证则相当于无中断。