深入理解Java多线程——线程池,
分享于 点击 17729 次 点评:231
深入理解Java多线程——线程池,
目录
- 定义
- 不同线程池的创建
- Executor的组成
- 线程池生命周期
- execute()方法
- 线程池大小的设置
- 线程池的使用
- 参考
定义
线程池,除了池的功能外,还提供了更全面的线程管理、任务提交等方法。
不同线程池的创建
JUC的Executors目前提供了5种不同的线程池创建配置
它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程。工作线程的创建数量有限制为Interger. MAX_VALUE。
- 如果工作线程空闲超过1分钟,将自动终止并移出缓存。长时间闲置时,这种线程池,不会消耗什么资源。
其内部使用SynchronousQueue作为工作队列
Executor的组成
Executor是一个基础的接口,其初衷是将任务提交和任务执行细节解耦,使开发者不被太多线程创建、调度等不相关细节所打扰。
void execute(Runnable command);
ExecutorService则更加完善,不仅提供service的管理功能,比如shutdown等方法,也提供了更加全面的提交任务机制,如返回Future而不是void的submit方法。
<T> Future<T> submit(Callable<T> task);
- 工作队列负责存储用户提交的各个任务,这个工作队列,可以是容量为0的SynchronousQueue(newCachedThreadPool),也可以是像固定大小线程池 (newFixedThreadPool),使用LinkedBlockingQueue。
private fnal BlockingQueue<Runnable> workQueue;
- 内部的“线程池”,指保持工作线程的集合,线程池需要在运行过程中管理线程创建、销毁。线程池的工作线程被抽象 为静态内部类Worker,基于AQS实现。
private fnal HashSet<Worker> workers = new HashSet<>();
- corePoolSize,所谓的核心线程数,可以大致理解为长期驻留的线程数目(除非设置了allowCoreThreadTimeOut)。对于不同的线程池,这个值可能会有很大区别,比
如newFixedThreadPool会将其设置为nThreads,而对于newCachedThreadPool则是为0。 - maximumPoolSize,顾名思义,就是线程不够时能够创建的最大线程数。同样进行对比,对于 newFixedThreadPool,当然就是nThreads,因为其要求是固定大小,而newCachedThreadPool则是Integer.MAX_VALUE 。
- keepAliveTime和TimeUnit,这两个参数指定了额外的线程能够闲置多久,显然有些线程池不需要它。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- ctl变量是一个非常有意思的设计,它被赋予了双重角色,通过高低位的不同,既表示线程池状态,又表示工作线程数目,这是一个典型的高效优化。
private fnal AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 真正决定了工作线程数的理论上限
private static fnal int COUNT_BITS = Integer.SIZE - 3;
private static fnal int COUNT_MASK = (1 << COUNT_BITS) - 1;
// 线程池状态,存储在数字的高位
private static fnal int RUNNING = -1 << COUNT_BITS;
…
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
private static int workerCountOf(int c) { return c & COUNT_MASK; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池生命周期
execute()方法
public void execute(Runnable command) {
…
int c = ctl.get();
// 检查工作线程数目,低于corePoolSize则添加Worker
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// isRunning就是检查线程池是否被shutdown
// 工作队列可能是有界的,ofer是比较友好的入队方式
if (isRunning(c) && workQueue.ofer(command)) {
int recheck = ctl.get();
// 再次进行防御性检查
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 尝试添加一个worker,如果失败以为着已经饱和或者被shutdown了
else if (!addWorker(command, false))
reject(command);
}
线程池大小的设置
如果我们的任务主要是进行计算,通常建议按照CPU核的数目N或者N+1。
如果是需要较多等待的任务,例如I/O操作比较多,可以参考Brain Goetz推荐的计算方法:线程数 = CPU核数 × (1 + 平均等待时间/平均工作时间)
线程池的使用
要注意:
参考
《Java并发实战》
《Java核心技术36讲》杨晓峰
相关文章
- 暂无相关文章
用户点评