java 线程池知识汇总.
线程池参数含义
- int corePoolSize
核心线程数,也即正常情况工作线程数
- int maximumPoolSize,
最大线程数
- long keepAliveTime,
需要结合阻塞队列来理解:假设阻塞队列的长度是3,核心数是2,最大线程数是5. 运行时是这样的:大于核心数时,会放到阻塞队列里面排队,如果队列满了才会启用新的工作线程,直到达到最大线程数
当达到最大线程数时,如果此时submit到线程池的任务变慢了,核心线程能够应对工作的话,这时线程池会动态减少工作线程数到核心线程数。这里的keepAliveTime 就是指 除核心线程以外的那几个线程的空闲时间。如果大于这个参数所指定的,线程池则会回收这些线程
- TimeUnit unit,
第三个参数的单位
- BlockingQueue<Runnable> workQueue,
比核心线程数多出来的线程会进入阻塞队列排队。别用 Executors.newFixedThreadPool 方法构造, 默认指定的阻塞队列(LinkedBlockingQueue)大小是Integer.MAX_VALUE,需显示指定
- ThreadFactory threadFactory,
用默认的就行,有需要的话区分下线程名字
- RejectedExecutionHandler handler
拒绝策略:
AbortPolicy(默认): 直接抛异常
DiscardPolicy: 随机丢弃
DiscardOldestPolicy: 丢弃最老的线程
CallerRunsPolicy:将执行权回退给调用者线程。
源码分析
- 预备知识(位运算)
- 原码(带符号位): 最高位为符号位, 负数为1,正数为0
- 反码: 原码除符号位,其余位取反.
- 补码(主要用于表示负数): 带符号的负数反码 + 1, 主要为了消除-0,只有负数才用补码表示. refer: https://www.zhihu.com/questio...
- 线程状态如何保存
线程池中,用前三位代表运行状态,用后29位代表工作线程数.
// 29
private static final int COUNT_BITS = Integer.SIZE - 3;
//1(原码表示)左移29位 + (-1)的补码 (32位1), 前三位都为0, 后29位为1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//即取后29位
private static int workerCountOf(int c) { return c & CAPACITY; }
//CAPACITY 取反即前三位是1, 后29位是0, 故任何数与 ~CAPACITY 做与运算都只保留前3位.
private static int runStateOf(int c) { return c & ~CAPACITY; }
- 任务管理
submit 提交任务后的执行逻辑
int c = ctl.get();
//获取后29位线程数,还没超过核心数, 则addWorker 更新线程数(ctl的后29位)
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//大于core线程数的话重新获取状态,线程池还在运行的话,则将任务放入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//线程池不在运行了的话,直接拒绝
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
//第一个线程为空,立即执行,见Worker类方法
addWorker(null, false);
}
//调用 addWorker 方法,更新ctl 运行线程数
else if (!addWorker(command, false))
reject(command);
- Worker 线程管理
为了管理工作线程的生命周期, 设计了worker 线程. 值得说一下的是Worker线程是一个AQS, 运行之前会上锁.运行结束之后会解锁
依赖于这一点,再想要中断这些idle线程的时候,可以通过是否能够上锁成功来判断工作线程是否正在运行.如果中断操作能上锁成功,则代表
线程没有再运行,这时就可以调用Worker线程的interrupt 方法中断线程,并且在hashSet里remove掉worker线程引用.等待jvm回收
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//getTask 从阻塞队列里阻塞拿到runnable, 然后运行
while (task != null || (task = getTask()) != null) {
//
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//如果 阻塞队列里面 任务为空, 则执行回收逻辑
//所有的worker 线程引用会保留在一个hashSet里面,
processWorkerExit(w, completedAbruptly);
}
}
//中断空闲线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//如果工作线程没有中断,并且上锁成功,那么就执行中断操作
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
其他
- 线程池配多少个好
从经验上讲需要区分是cpu密集型还是io密集型。 cpu密集型的话,为了避免上下文切换,数量不宜过多,一般为cpu核数 + 1 ;
如果是io密集型的话,可以多点,需要大致明确 io型任务耗时和cpu密集型任务耗时的比例.
- 死锁编码和定位
jstack 排查
- ExecutorService.submit(Runnable task, T result) 应该这样用
result 作为子线程和主线程沟通的桥梁,作为构造参数传入 task中. 子线程可以操作result对象.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。