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对象.