1. 引言
线程池在Java开发及第三方开源框架中使用的十分广泛,所以对于线程池不只要学会使用还要理解其实现细节,这样在使用过程中才会得心应手,不至于跟风。我们学习一门技术,既要知其然也要知其所以然,换句话说知道一个技术能做什么和不能做什么,我们在做技术选型的时候才会更加游刃有余。
2 Java 自带的线程池
2.1 SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2.2 FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.3 CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2.4 小结
通过以上,不难看出其实底层源码创建的都是ThreadPoolExecutor,只是各种不同的线程池传入的参数不一样而已。
3. Spring 提供的线程池
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor;
if (this.taskDecorator != null) {
executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler) {
@Override
public void execute(Runnable command) {
super.execute(taskDecorator.decorate(command));
}
};
}
else {
executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler);
}
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
可以看到, 不论是Spring提供的线程池还是Java自带的线程池都是建立在Java 的ThreadPoolExecutor的基础上的,所以阅读其源码就很有必要了。
4.ThreadPoolExecutor
4.0 关于2进制的运算
由于线程池的实现中用到了很多2进制及位运算,这里给大家补充点已经遗忘的知识哈。
> 2进制如果要区分正负的话,最高位是0则为正,反之为负
> 计算机存储的是2进制数的补码,正数的补码是其本身,负数补码为原码按位取反(符号为不变),再加1
操作符 | 操作数1(2进制) | 操作数2(2进制) | 结果(2进制) |
& | 1001 | 0101 | 0001 |
| | 1001 | 0101 | 1101 |
~ 或者 ! | 1001 | 0110 | |
<< 2 | 0001 | 0100 | |
>> 1 | 0110 | 0011 | |
补码 | 0100 | 0100 | |
补码 | 1001 | 1111 |
4.1 构造参数
这里先给出各个参数的含义:
名称 | 含义 |
corePoolSize | 线程池核心线程数(常驻线程数),就算空闲也不会回收 |
maximumPoolSize | 线程池最大线程数,当核心线程不足以处理任务时启用,空闲时回收 |
keepAliveTime | (maximumPoolSize - corePoolSize) 存活时间 |
unit | 时间单位,配合keepAliveTime一起使用, 如keepAliveTime设置为60, unit 为秒时,存活60s |
workQueue | 任务队列,在核心线程数用完后,如果继续向线程池提交任务,任务被暂存到workQueue中,当workQueue塞满时,开始增加线程数处理 |
threadFactory | 线程工厂,用于定义如何生成线程池里的线程,设置线程名称等 |
handler | 拒绝策略,自带的有丢弃(不处理),抛异常,扔掉排队最久的,直接用当前线程执行(提交任务的线程),当然也可以自定义,这只是个兜底操作,需要做权衡了 |
4.2 线程池内常量初始值
ThreadPoolExecutor以int类型的高3位做为整个线程池的状态标记码,所以最大线程数是不能高于
2^29 -1(从第1位到第28位的最大和),这个数也就是整个线程池的Capcity。
名称 | 二进制 | 十进制 |
RUNNING | 11100000000000000000000000000000 | -536870912 |
SHUTDOWN | 00000000000000000000000000000000 | 0 |
STOP | 00100000000000000000000000000000 | 536870912 |
TIDYING | 01000000000000000000000000000000 | 1073741824 |
TERMINATED | 01100000000000000000000000000000 | 1610612736 |
CAPACITY | 00011111111111111111111111111111 | 536870911 |
~CAPACITY | 11100000000000000000000000000000 | -536870912 |
ctl | 11100000000000000000000000000000 | -536870912 |
4.3 任务执行流程
ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService又实现了ExecutorService和Executor,所以ThreadPoolExecutor也是ExecutorService的实现类。
4.3.1 提交任务到线程池
通过线程池提交任务,最终都会调用到execute方法,所以分析execute方法实现才是关键
AbstractExecutorService部分代码
public abstract class AbstractExecutorService implements ExecutorService {
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
}
4.3.2 执行任务
线程池的几个位运算方法
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; }
execute的具体实现细节位于ThreadPoolExecutor类,代码如下
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 初次提交 ctl 值为 11100000000000000000000000000000
int c = ctl.get();
/*
* 线程池内的线程小于 corePoolSize
* 初次提交
* workerCountOf = (11100000000000000000000000000000 & 00011111111111111111111111111111) = 0
*/
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// corePoolSize 已经使用完成,将提交的任务存放到对列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 线程池处于异常状态,移除当前任务
if (! isRunning(recheck) && remove(command))
//移除失败,执行拒绝策略
reject(command);
// corePoolSize 设置为0 的情况
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 队列存满,提升当前线程数
else if (!addWorker(command, false))
// 对列已满,线程池内的线程数已达最大线程数,执行拒绝策略
reject(command);
}
4.3.3 线程池核心方法addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// c: 11100000000000000000000000000000 (-536870912)
int c = ctl.get();
/*
*rs = c & ~CAPACITY(11100000000000000000000000000000)
*rs = 11100000000000000000000000000000 (-536870912)
*/
int rs = runStateOf(c);
// 正常执行时,跳过此条判断即可
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) return false;
for (;;) {
/*
*wc = c & CAPACITY(00011111111111111111111111111111)
*wc = 0
*/
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
// c + 1, 即 wc + 1, 成功后直接退出外层循环
if (compareAndIncrementWorkerCount(c)) break retry;
c = ctl.get();
if (runStateOf(c) != rs) continue retry;
}
}
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 {
// 再次获取线程池状态, 正常情况下依旧是 RUNNING(小于0)
int rs = runStateOf(ctl.get());
// SHUTDOWN: 0
if (rs < SHUTDOWN ||(rs == SHUTDOWN && 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();
}
// 添加成功后, 启动worker
if (workerAdded) {
// 执行worker 的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) addWorkerFailed(w);
}
return workerStarted;
}
4.3.4 创建工作线程(暂且这么叫吧)
这里特别注意this的含义,它指向了正在创建的worker对象,worker 本身也是一个Runnnable, 所以它能够传入Thread中。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
// 调用线程工厂,创建线程
this.thread = getThreadFactory().newThread(this);
}
}
public int getPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Remove rare and surprising possibility of
// isTerminated() && getPoolSize() > 0
return runStateAtLeast(ctl.get(), TIDYING) ? 0 : workers.size();
} finally {
mainLock.unlock();
}
}
以上代码代码可以看出,其实ThreadPoolExecutor里直接维护的其实并不是线程,而是自己的Worker。而这个Worker其实并不是线程,只是持有线程变量而已。
4.3.5 执行工作线程
在worker成功添加后会调用t.start(), 而worker中t中的task是woker本身,所以t.start()调用后会执行worker的run方法(而不是我们提交的任务,这里要注意了)。
Worker 的run方法就一行代码如下
public void run() {
// 调用外部类方法,同时传入worker本身
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 有两个来源
*1. worker 传入
* > worker 小于 corePoolSize
* > corePoolSize已满,队列已满,当前线程数小于 maxPoolSize
*2. corePoolSize已满后,队列未满时,存入队列的task
* getTask 方法在无任务提交时会阻塞
*/
while (task != null || (task = getTask()) != null) {
// worker 自己加锁,防止同一个worker被多次调用
w.lock();
// 正常情况下,跳过此条判断
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
&& !wt.isInterrupted())
wt.interrupt();
try {
// 扩展接口,ThreadPoolExecutor 中未实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 直接调用提交task的run方法, 因为已经处于工作线程中
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 {
// 扩展接口,ThreadPoolExecutor 中未实现
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
至此我们提交任务到线程池,到线程池执行我们提交的任务整个flow已经追踪完成。
4.3.6 线程池内线程数的维护
如果线程池还有未完成的任务,那么线程池至少会维护一个线程,即便是corePoolSize是0,或者将allowCoreThreadTimeOut设置为true。对于那些处于corePoolSize和maxPoolSize之间的线程在等待了keepAliveTime时间后会逐个关闭。具体可以看线程回收的代码,如下。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
// 统计每个工作线程执行的任务数
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// 检查线程池状态,正常时为 RUNNING
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// 维护最小线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && !workQueue.isEmpty()) min = 1;
if (workerCountOf(c) >= min) return;
}
// 添加工作线程, 最少维护corePoolSize个线程
addWorker(null, false);
}
}
线程池corePoolSize与maxPoolSize的维护在getTask()方法内部。
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
// 检查线程池状态
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 获取worker线程数
int wc = workerCountOf(c);
// 过期设置是否生效
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 如果已经过期,回收多余线程, 如果设置了allowCoreThreadTimeOut在时间无任务提交时
* 线程池内将不再有存活的线程,这样面对瞬间大流量时,线程池可能会卡死,甚至崩掉(一直开线程)
*/
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
// 将工作线程数减1
if (compareAndDecrementWorkerCount(c)) return null;
continue;
}
try {
/*
* 从队列获取要执行的任务 2 种方式
* 1. 对于过期设置生效的情况,使用有时间限制的获取
* 2. 对于过期设置未生效的情况,使用无限制
* 过期设置如何生效:
* 1)线程数大于 corePoolSize
* 2)allowCoreThreadTimeOut 为 true
*/
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null) return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
4.3.7 线程池自带的几种拒绝策略
当线程池内线程数达到最大线程数,且队列已满时,线程池会执行拒绝策略。ThreadPoolExecutor提供了几种拒绝策略我们来一起看看吧。
策略1: 抛出异常(默认策略)
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
策略2: 丢弃当前提交任务(无视、不处理)
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
策略3: 丢弃队列中排队最久的任务(队头元素)
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
策略4: 使用提交任务的线程执行任务(相当于无并发了)
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
当然我们也可以自己实现RejectedExecutionHandler来自定义自己的拒绝策略,比如将task缓存到本地memory, 或者固化到磁盘,等到流量高峰过去后在执行。不过在我看来,与其纠结于拒绝策略不如考虑如何不让这种情况发生。现在这种技术很多了,限流就是一个很好的实现,让进入系统内部的流量肯定能被系统消化,这样就不会执行拒绝策略了。限流的实现方式很多,这里不做讨论了。
5. 线程池属性观测及分析
5.1 提交任务数小于等于corePoolSize
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class ThreadDemo {
@Test
void test_thread_pool() throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
Runnable task = () -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < 2; i++) {
System.out.println("提交前线程数:" + threadPool.getPoolSize());
System.out.println("提交前队列任务数:" + threadPool.getQueue().size());
threadPool.submit(task);
System.out.println("提交后线程数:" + threadPool.getPoolSize());
System.out.println("提交后队列任务数:" + threadPool.getQueue().size());
}
threadPool.awaitTermination(60, TimeUnit.SECONDS);
}
}
输出结果:
提交前线程数:0
提交前队列任务数:0
提交后线程数:1
提交后队列任务数:0
提交前线程数:1
提交前队列任务数:0
提交后线程数:2
提交后队列任务数:0
从结果可以看出, corePoolSize已经使用完毕,但队列为空。
5.2 提交任务数小于 corePoolSize + queue size
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class ThreadDemo {
@Test
void test_thread_pool() throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
Runnable task = () -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < 10; i++) {
System.out.println("提交前线程数:" + threadPool.getPoolSize());
System.out.println("提交前队列任务数:" + threadPool.getQueue().size());
threadPool.submit(task);
System.out.println("提交后线程数:" + threadPool.getPoolSize());
System.out.println("提交后队列任务数:" + threadPool.getQueue().size());
}
threadPool.awaitTermination(60, TimeUnit.SECONDS);
}
}
输出结果:
提交前线程数:0
提交前队列任务数:0
提交后线程数:1
提交后队列任务数:0
提交前线程数:1
提交前队列任务数:0
提交后线程数:2
提交后队列任务数:0
提交前线程数:2
提交前队列任务数:0
提交后线程数:2
提交后队列任务数:1
提交前线程数:2
提交前队列任务数:1
提交后线程数:2
提交后队列任务数:2
提交前线程数:2
提交前队列任务数:2
提交后线程数:2
提交后队列任务数:3
提交前线程数:2
提交前队列任务数:3
提交后线程数:2
提交后队列任务数:4
提交前线程数:2
提交前队列任务数:4
提交后线程数:2
提交后队列任务数:5
提交前线程数:2
提交前队列任务数:5
提交后线程数:2
提交后队列任务数:6
提交前线程数:2
提交前队列任务数:6
提交后线程数:2
提交后队列任务数:7
提交前线程数:2
提交前队列任务数:7
提交后线程数:2
提交后队列任务数:8
从输出结果不难看出,在corePoolSize使用完毕后,优先往队列里存放task。
5.3 提交任务小于 maxPoolSize + queue size
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class ThreadDemo {
@Test
void test_thread_pool() throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
Runnable task = () -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < 13; i++) {
System.out.println("提交前线程数:" + threadPool.getPoolSize());
System.out.println("提交前队列任务数:" + threadPool.getQueue().size());
threadPool.submit(task);
System.out.println("提交后线程数:" + threadPool.getPoolSize());
System.out.println("提交后队列任务数:" + threadPool.getQueue().size());
}
threadPool.awaitTermination(60, TimeUnit.SECONDS);
}
}
输出结果:
提交前线程数:0
提交前队列任务数:0
提交后线程数:1
提交后队列任务数:0
提交前线程数:1
提交前队列任务数:0
提交后线程数:2
提交后队列任务数:0
提交前线程数:2
提交前队列任务数:0
提交后线程数:2
提交后队列任务数:1
提交前线程数:2
提交前队列任务数:1
提交后线程数:2
提交后队列任务数:2
提交前线程数:2
提交前队列任务数:2
提交后线程数:2
提交后队列任务数:3
提交前线程数:2
提交前队列任务数:3
提交后线程数:2
提交后队列任务数:4
提交前线程数:2
提交前队列任务数:4
提交后线程数:2
提交后队列任务数:5
提交前线程数:2
提交前队列任务数:5
提交后线程数:2
提交后队列任务数:6
提交前线程数:2
提交前队列任务数:6
提交后线程数:2
提交后队列任务数:7
提交前线程数:2
提交前队列任务数:7
提交后线程数:2
提交后队列任务数:8
提交前线程数:2
提交前队列任务数:8
提交后线程数:2
提交后队列任务数:9
提交前线程数:2
提交前队列任务数:9
提交后线程数:2
提交后队列任务数:10
提交前线程数:2
提交前队列任务数:10
提交后线程数:3
提交后队列任务数:10
从上面的结果可以看到, 当对列也被塞满时,线程池开始新加线程来处理新任务。
5.4 提交任务大于 maxPoolSize + queue size
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class ThreadDemo {
@Test
void test_thread_pool() throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
Runnable task = () -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < 16; i++) {
System.out.println("提交前线程数:" + threadPool.getPoolSize());
System.out.println("提交前队列任务数:" + threadPool.getQueue().size());
threadPool.submit(task);
System.out.println("提交后线程数:" + threadPool.getPoolSize());
System.out.println("提交后队列任务数:" + threadPool.getQueue().size());
}
threadPool.awaitTermination(60, TimeUnit.SECONDS);
}
}
执行结果:
从结果可以看到,线程池接收并处理了前15个任务,但是当第16个任务提交时,线程内部已无资源可用,所以执行了拒绝策略(默认抛异常),拒绝了任务的提交。
5.5 多余线程的回收
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class ThreadDemo {
@Test
void test_thread_pool() throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
Runnable task = () -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < 14; i++) {
threadPool.submit(task);
}
while(true) {
System.out.println("线程数:" + threadPool.getPoolSize());
System.out.println("队列任务数:" + threadPool.getQueue().size());
Thread.sleep(10000);
}
}
}
输出结果:
线程数:4
队列任务数:10
线程数:4
队列任务数:8
线程数:4
队列任务数:3
线程数:4
队列任务数:0
线程数:4
队列任务数:0
线程数:2
队列任务数:0
线程数:2
队列任务数:0
通过观察如上结果,如果线程数增加后一直任务可以处理,那么一直不会回收线程。只有在等待超时(过了keepAlivetTime)后线程才会被回收,释放所占用的系统资源。一般在自定义线程时,建议将最大最小设置为一样,这样一方面可以避免线程数在最大最小之前跳跃,带来系统性能的不稳定性,还可以避免因最大线程数设置的过大,而在瞬时创建大量线程而占用过多系统资源的情况。
5.6 二进制下线程池内各属性变化过程
先来看看如下table
名称 | 二进制 | 十进制 |
RUNNING | 11100000000000000000000000000000 | -536870912 |
SHUTDOWN | 00000000000000000000000000000000 | 0 |
STOP | 00100000000000000000000000000000 | 536870912 |
TIDYING | 01000000000000000000000000000000 | 1073741824 |
TERMINATED | 01100000000000000000000000000000 | 1610612736 |
CAPACITY | 00011111111111111111111111111111 | 536870911 |
~CAPACITY | 11100000000000000000000000000000 | -536870912 |
ctl | 11100000000000000000000000000000 | -536870912 |
在这个表格中会跟随我们提交任务而变化的属性只有一个ctl, 我们来模拟下我们向线程池提交任务的过程。
假设线程池 corePoolSize=2, maxPoolSize=5, queue size =10
初始时
名称 | 二进制数 | 十进制 | 算法 |
CAPACITY | 00011111111111111111111111111111 | 536870911 | |
~CAPACITY | 11100000000000000000000000000000 | -536870912 | |
ctl | 11100000000000000000000000000000 | -536870912 | |
wc(worker count) | 00000000000000000000000000000000 | 0 | c & CAPACITY |
rs (running status) | 11100000000000000000000000000000 | -536870912 | c & ~CAPACITY |
提交一次任务后
名称 | 二进制数 | 十进制 | 算法 |
CAPACITY | 00011111111111111111111111111111 | 536870911 | |
~CAPACITY | 11100000000000000000000000000000 | -536870912 | |
ctl | 11100000000000000000000000000001 | -536870911 | |
wc(worker count) | 00000000000000000000000000000001 | 1 | c & CAPACITY |
rs (running status) | 11100000000000000000000000000000 | -536870912 | c & ~CAPACITY |
提交两次后
名称 | 二进制数 | 十进制 | 算法 |
CAPACITY | 00011111111111111111111111111111 | 536870911 | |
~CAPACITY | 11100000000000000000000000000000 | -536870912 | |
ctl | 11100000000000000000000000000010 | -536870910 | |
wc(worker count) | 00000000000000000000000000000010 | 2 | c & CAPACITY |
rs (running status) | 11100000000000000000000000000000 | -536870912 | c & ~CAPACITY |
提交三次时,由于core线程数用完,任务放入队列中,不会再增加worker count。
提交13次后
名称 | 二进制数 | 十进制 | 算法 |
CAPACITY | 00011111111111111111111111111111 | 536870911 | |
~CAPACITY | 11100000000000000000000000000000 | -536870912 | |
ctl | 11100000000000000000000000000011 | -536870909 | |
wc(worker count) | 00000000000000000000000000000011 | 3 | c & CAPACITY |
rs (running status) | 11100000000000000000000000000000 | -536870912 | c & ~CAPACITY |
好了整体的变化过程大致就是这样,大家在看源码时,如果涉及到二进制的操作,直接看二进制会容易理解很多了。
6. 关于线程池使用的一些建议
近些年来,我经常听到有人来黑Java自带的线程池,说是在坚决杜绝在项目中使用云云。在我看来,大部分人都是在跟风,人云亦云,根本没有理解其技术要点。
分析完源码我们来看看Java提供的快速线程池到底有何罪?
1) SingleThreadExecutor与FixedThreadPool是一样的唯一的区别就在于线程数量,而且其内部使用的是无界队列。这也没什么好说的,因为最大线程数就是最小线程数,也没有可扩展的余量了,但是无界队列会引起另一个问题,因为无界所以任务会一直存放到线程池内部,不会触发拒绝策略,这种极端情况最后后导致APP内存耗尽(全存了task了),最后APP崩溃;
2) CachedThreadPool 内部使用的SynchronousQueue, 这种队列只有1的容量放了就得取,所以在这种实现下,当有大量任务到来时就会开出来很多线程来执行任务,因为是开线程处理就不会触发拒绝策略,最终将导致系统内瞬时线程数飙升(创建线程也需要时间),系统变慢,或者线程数太多从而导致APP崩溃。
当然这些情况都是在极端情况下才会遇到,还是我之前说的那些,与其将工作中的放在这,不如考虑如何限流才是重中之重。
对于那些只有几十个任务,或者用线程池去并发处理一些文件这种任务本身就不会有多么高的并发量,自带的线程池已然够用。
了解技术内幕,在系统设计时根据自己APP的情况合理的选择才是最重要的,每一个技术有其适用的场景,要学会合理使用。
7. 总结
本文花了大量的篇幅来详细分析了Java线程池ThreadPoolExecutor的执行过程,希望大家能理解处理和调度任务的过程。由于时间和篇幅的原因,我只介绍了Running状态下线程池的运行过程,至于其他的状态我想要比Running状态要容易一些,这些状态的执行过程就留给有兴趣深入研究的小伙伴去分析了哈。
线程池是Java多线程并发的核心,希望本篇文章能让你对它有清晰的认识。活学活用,把它应用到自己日常的开发工作中去,提升自己的开发能力了,再次感谢各位阅读本篇文章了。