线程池详解
1. 线程池的意义
线程是稀缺资源,它的创建与销毁是比较重且耗资源的操作。而Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免资源过度消耗需要设法重用线程执行多个任务,线程池就是一个线程缓存,负责对线程进行统一分配,调优与监控。
线程池的优势:
- 重用存在的线程,减少线程创建、消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行任务。
- 提高线程的可管理性。线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2. 线程池的创建
-
Executors.newFixedThreadPool() newWorkStealingPool() newSingleThreadExecutor() newCachedThreadPool() newScheduledThreadPool()
(极不推荐)通过Executors的静态方法初始化线程池
-
ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue )
推荐使用,可以自定义线程池的参数。以下线程池也都指该类。
3. ThreadPoolExecutor 源码
3.1 源码注释
- 使用线程池的线程来完成任务,一般使用Executors工厂方法来配置
- 线程池一般用来解决两个好处
- 线程重复利用,减少创建线程个数,提高性能
- 提供限定和管理资源的手断
- 该类提供了很好的拓展性,但一般我们使用Executors工厂方法就可以创建出三种非常好用的线程池了
- 自定义线程池
- 如果运行的线程少于corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的
- 如果运行的线程大于corePoolSize,小于maximumPoolSize,则仅当队列满时才创建新线程
- 如果设置的corePoolSize和maximumPoolSize相同,则创建了固定大小的线程池
- 如果设置了maximumPoolSize最大值,那么允许池适应任意数量的并发任务
3.2 属性
//使用原子类Integer存储线程池属性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//高3位存储运行状态
private static final int COUNT_BITS = Integer.SIZE - 3;
//后29位存储容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//111=RUNNING
private static final int RUNNING = -1 << COUNT_BITS;
//000=SHUTDOWN
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001=STOP
private static final int STOP = 1 << COUNT_BITS;
//010=TIDYING
private static final int TIDYING = 2 << COUNT_BITS;
//011=TERMINATED
private static final int TERMINATED = 3 << COUNT_BITS;
//获取运行状态,ctl & (1110 ...)
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取容量,ctl & (0001 1111...)
private static int workerCountOf(int c) { return c & CAPACITY; }
//将运行状态和池容量做或运算,得出ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池的状态:
- RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理
- SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理
- STOP:线程池不接受新任务,不处理已添加的任务,并且会中断当前正在处理的任务
- TIDYING:当所有的任务已终止,ctl记录的“任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated。terminated在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的数量,可以通过重载terminated函数来实现
- TERMINATED:线程池彻底终止的状态
3.3 默认实现的池
-
new FixedThreadPool
一个固定线程数的线程池,返回一个corePoolSize和maximumPoolSize相等的线程池
-
newCachedThreadPool
非常有弹性的线程池,对于新的任务,如果此时线程池里没有空闲线程,线程池会毫不犹豫地创建一条新的线程去处理这个任务。
-
SingleThreadExecutor
使用单个worker线程
-
ScheduledThreadPoolExecutor
可以用来在给定延时后执行异步任务或者周期性执行任务
-
ForkJoinPool
ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时,最好配合使用 ManagedBlocker。
newWorkStealingPool就是ForkJoinPool的实现类
3.4 方法
-
构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {} 1. corePoolSize 指定核心线程数量 2. maximumPoolSize 指定最大线程数量 3. keepAliveTime 允许线程空闲时间 4. unit 时间对象 5. workQueue 阻塞队列 6. threadFactory 线程工厂 7. handler 任务拒绝策略
-
线程数量要点:
- 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求
- 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程
- 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池
- 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量
-
线程空闲时间要点:
- 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁。
-
排队策略要点:
- 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。
- 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数
- 有界限策略:可以避免资源耗尽,但是一定程度上减低了吞吐量
当线程关闭或者线程数量满了和队列饱和了,就有拒绝任务的情况了:
-
拒绝任务策略:
- 直接抛出异常
- 直接丢掉这个任务
- 丢掉最老的任务
- 使用调用者的线程来处理
-
-
execute方法
//向线程池中添加任务 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); 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(); //当线程池不属于Running状态时,删除线程 if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } //核心线程和队列都满,尝试添加线程 else if (!addWorker(command, false)) reject(command); } private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); //当运行状态不为Running时,或者处于shutdown,stop,tiyding,terminated时,返回false if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); //线程数大于容量,或者大于核心线程池或最大线程池大小时,返回false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //更新线程池线程数量 if (compareAndIncrementWorkerCount(c)) break retry;//跳出循环 c = ctl.get(); // Re-read ctl //数据不同步时,重新循环 if (runStateOf(c) != rs) continue retry; } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //创建一个Worker对象 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; //获取主线程锁 mainLock.lock(); //更新workers(HashSet) try { int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable 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 { //启动不成功则删除线程 if (! workerStarted) addWorkerFailed(w); } return workerStarted; } //向队列中添加 public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } }
-
shutdown()
//不再接受新任务,按队列中的顺序,将已有线程执行完毕 public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); //设置线程池状态为Shutdown advanceRunState(SHUTDOWN); //向线程发出中断请求 interruptIdleWorkers(); //钩子函数 onShutdown(); } finally { mainLock.unlock(); } //尝试终止 tryTerminate(); }
-
shutdownNow()
//不再接受新的任务和队列中的任务,且将当前线程中断 //返回等待任务的队列 public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); } finally { mainLock.unlock(); } //尝试终止 tryTerminate(); return tasks; }