Java 基础篇之线程池
- 线程池的主要工作流程是什么?
核心代码:ThreadPoolExecutor类
public void execute(Runnable command) {
//如果任务为null,抛出空指针异常
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
//获取当前线程池的状态+线程个数变量的组合值
int c = ctl.get();
//1.如果当前有效线程数 < 核心线程数,调用addWoker执行任务(即创建一个线程执行任务)
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.如果当前有效线程数 > 核心线程数,并且当前线程池状态为运行状态,同时尝试非阻塞方法向任务队列放入任务(放入失败返回false)
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);
}
//3.如果阻塞队列已满,则调用addWorker执行任务(即创建一条线程执行该任务)
else if (!addWorker(command, false))
//如果创建线程失败,则调用线程拒绝策略
reject(command);
}
-
线程池有哪几种工作队列?怎么理解无界队列和有界队列?
三种队列:
- ArrayBlockingQueue:有界队列(基于数组的)
- LinkedBlockingQueue:有/无 界队列(基于链表的,传参就有界,不传参就无界[默认Integer.MAX_VALUE])
- SynchronousQueue:同步队列(不存储元素的阻塞队列)
有界队列:
有固定大小的队列。例如设定了固定大小的LinkedBlockingQueue,大小为0在生产者和消费者中做中转用的SynchronousQueue。
无界队列:
没有设置固定大小的队列。可以直接入列,直到溢出,默认为Integer.MAX_VALUE(2147483647).
-
线程池的拒绝策略有何用途,有哪些拒绝策略?是否可以自定义拒绝策略?
拒绝策略是对线程池起限流保护的作用。
有4种拒绝策略:
1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
😃可以根据实际应用场景实现RejectedExecutionHandler接口,自定义拒绝策略,如记录日志或持久化存储不能处理的任务,便于定位问题,分析问题。
public class Test { public static class MyTask implements Runnable { @Override public void run() { System.out.println("thread id:" + Thread.currentThread().getId()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args){ LinkedBlockingDeque queue = new LinkedBlockingDeque(4); ExecutorService es = new ThreadPoolExecutor( 5, 5, 0L, TimeUnit.MILLISECONDS, queue, Executors.defaultThreadFactory(), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("这里是自定义的线程拒绝策略"); } } ); MyTask task = new MyTask(); for (int i=0; i<100; i++) { es.submit(task); } } } 结果: thread id:12 thread id:16 这里是自定义的线程拒绝策略
-
如何创建、停止线程池?为什么不建议使用Executors构建线程池?
① 线程池创建:
1. 使用ThreadPoolExecutor创建,传递线程池的各种参数
2. 使用Executors创建,调用不同的线程池类型(例如newFixedThreadPool)
② 线程池终止:
1.优雅型退出-shutdown()
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不接受新的任务,它会等待所有任务执行完毕。
2.强迫型退出-shutdownNow()
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不接受新的任务,并且会去尝试终止正在执行的任务。
③ 为什么不建议使用Executors构建线程池
因为Executors 提供的很多方法默认使用的都是无界的LinkedBlockingQueue,高负载情况下,无界队列很容易导致OOM(内存溢出),因此强烈建议使用有界队列。
-
线程池有哪些种类,各自的使用场景是什么?
① newSingleThreadPool:单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行
② newFixedThreadPool:固定大小的线程池,用于已知并发压力的情况下,对线程数做限制
③ newCachedThreadPool:一个可缓存的线程池,比较适合处理执行时间比较小的任务
④ newScheduledThreadPool:适用于定时以及周期性执行任务的场景
⑤ newWorkStealingThreadPool:jdk1.8提供的线程池,底层使用ForkJoinPool实现,适用于大任务分解并行执行的场景。Fork/Join框架用到了工作窃取(work-stealing)算法,任务分割为若干互不依赖的子任务。
-
线程池有哪些状态,状态的设计机制是什么,状态是如何相互切换的?
① 状态
-
RUNNING:接受新任务并且处理阻塞队列里的任务
-
SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务
-
STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理任务
-
TIDYING:所有任务都执行完(包含阻塞队列里面的任务),当前线程池活动线程为0,将要调用terminated方法
-
TERMINATED:终止状态,terminated方法调用完成以后的状态
/* The runState provides the main lifecycle control, taking on values: * * RUNNING: Accept new tasks and process queued tasks * SHUTDOWN: Don't accept new tasks, but process queued tasks * STOP: Don't accept new tasks, don't process queued tasks, * and interrupt in-progress tasks * TIDYING: All tasks have terminated, workerCount is zero, * the thread transitioning to state TIDYING * will run the terminated() hook method * TERMINATED: terminated() has completed /
② 状态设计机制
使用一个AtomicInteger类型的变量ctl,来封装线程池的状态以及起当前活动的线程数
ctl 一共由32位,其中高3位来表示线程池的状态:
111:RUNNING
000:SHUTDOWN
001:STOP
010:TIDYING
011:TERMINATED
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
③ 状态是如何切换的
-
/* RUNNING -> SHUTDOWN
* On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
* On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
* When both queue and pool are empty
* STOP -> TIDYING
* When pool is empty
* TIDYING -> TERMINATED
* When the terminated() hook method has completed
/
-
RUNNING -> SHUTDOWN:显式调用shutdown()方法,或者隐式调用了finalize(),它里面调用了shutdown()方法
-
RUNNING or SHUTDOWN -> STOP:显式调用shutdownNow()方法
-
SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候
-
STOP -> TIDYING:当线程池为空的时候
-
TIDYING -> TERMINATED:当terminated() 方法执行完毕之后
-
线程池的使用场景,线程池为什么能提升性能?
① 使用场景
适用于高并发,批量处理,性能调优等场景
② 为什么提升性能
节省线程创建、销毁的时间
-
线程池有哪些重要参数,如何设置这些重要参数?
① 重要参数
- corePoolSize:线程池的核心线程数,即使线程池里没有任何任务,也会有corePoolSize个线程在候着等任务
- maximumPoolSize:线程池的最大线程数,不管有多少任务,线程池最多工作线程数就是maximumPoolSize
- keepAliveTime:线程存活的时间,当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行时,则线程退出
- unit:用来指定keepAliveTime的单位
- workQueue:队列,提交的任务将会被放入这个队列里
- threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字
- handler:拒绝策略,当线程池里的线程被耗尽,且队列也满了的时候会调用
② 如何设定这些参数
-
参考Executors类设置,测试
-
CPU密集型:计算密集型,大部分时间用来做计算逻辑判断等CPU操作,尽量减少CPU上下文切换,核心线程数大小 = Ncpu + 1 (N 几核)
-
IO密集型:任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较小,核心线程数=2Ncpu
-
线程池如何获取执行返回的结果?
① 如果是同步获取结果:入口方法是execute(Runnable command)
② 如果是异步获取结果:ThreadPoolExecutor 类的 submit 方法
-
出现unable to create new native thread的异常,如何分析解决?
原因:
创建线程数超过了操作系统的限制
分析:
线程数设置是否合理,是否存在多个定时调度任务,且线程数太大
解决:
设置合理的线程数,加机器等