Java线程池的执行流程图:
- 创建线程池,默认情况池子里面的线程数是为0,当有任务来的时候创建第一个线程。
- 当线程池中的工作线程数是否达到核心线程数后,如果未达到核心线程数,则创建一个核心工作线程执行任务。
- 如果已达核心线程数则将任务添加到阻塞对列中。
- 阻塞对列是否已满,没有满,直接add到阻塞对列,等待被执行。
- 如果阻塞队列已经满了,则比较当前工作线程数是否超过最大线程数,没有超过,创建一个非核心工作线程并执行任务,任务执行结束后会回收掉。
- 如果超过了最大线程数,将执行拒绝策略,默认策略是 AbortPolicy,可自定义拒绝策略。
Java线程池有哪几种:
1. newSingleThreadPool
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务,
LinkedBlockingQueue 阻塞队列
2. newFixedThreadPool
固定数量的线程池,用于已知并发压力的情况下,对线程数做限制,每提交一个任务就是一个线程,直到线程达到线程池的最大数量,超过最大数量的任务将进入阻塞队列,直到前面的任务完成后才继续执行;
核心线程数 = 最大线程数,是相等的看下面源码。
LinkedBlockingQueue 阻塞队列
3. newCachedThreadPool
可缓存线程池,一般60秒无执行的线程会被回收掉,当有任务时,会添加新线程来执行;
没有核心线程数,最大线程数是Interger最大值,超时时间60秒。一般都看作是无限制的线程池。
keepAliveTime = 60s
SynchronousQueue 阻塞队列
4. newScheduledThreadPool
特殊的线程池,支持定时和周期性的执行线程,实际业务场景中可以使用该线程池定期的同步数据。
自定义的核心线程数,最大线程数是Interger最大值,超时时间60秒。一般都看作是无限制的线程池。
5. newSingleThreadScheduledExecutor
只有一个工作线程,可以按时间执行
6.newWorkStealingPool
JDK 1.8 后添加的,一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
前四种比较常用。
Java线程池的核心参数
corePoolSize: 核心线程数
1.核心线程数会一直存活,及时没有任何任务需要执行。
2.当工作线程数小于核心线程数时,即使有空闲线程,线程池也会优先创建新的线程处理。
3. 设置allowcoreThreadTimeout = true时,核心线程会超时关闭。
maximumPoolSize: 最大线程数
核心线程数满了 阻塞队列满了 这是线程数还不到最大线程数,可以new线程继续工作,这里new的线程就行临时工一样,run方法执行完后就是销毁线程
keepAliveTime: 空闲时间
1.当前线程达空闲时间达到keepAliveTime时,线程会退出,当前工作线程数量 => corePoolSize时, 则不会退出。
2.如果allowCoreThreadTimeout = true, 那所有的工作线程超时后都会退出。
nuit: 时间类型
workQueue: 阻塞队列
defalutHandler: 拒绝策略处理器
拒绝策略
AbortPolicy策略(默认值),丢弃任务,抛出运行时异常信息
CallerRunsPolicy策略,执行任务。
DiscardPolicy策略,忽视,什么都不做
DiscardOldestPolicy策略,从队列中踢出最靠前的一个元素,并执行它。
Queue 中 remove() 和 poll()都是用来从队列头部删除一个元素。
在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。
自定义拒绝策略, 实现RejectedExecutionHandler,重写rejectedExecution方法。
何时会触发拒绝策略
- 当前核心线程数已满,阻塞队列已满,最大线程数也满了,会拒绝新的任务。
- 当线程池被调用shotdown()方法后,会等线程池里的任务执行完毕后在shotdown操作。如果在调用shotdown()和线程真正shotdown之间提交任务吗,会拒绝新任务。
为什么不建议使用 Executors静态工厂构建线程池
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
具体说明:
- FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
正确创建线程池
避免使用Executors创建线程池,主要是避免使用其中的默认实现(Executor好多核心参数都隐藏了,不利于我们只管的使用,后期修改也是麻烦)。那么我们直接使用ThreadPoolExecutor类的构造器来创建线程池,BlockQueue也顺便指定合适的值了。
private static ExecutorService executor = new ThreadPoolExecutor(10,15,60L,TimeUnit,SECONDS, new ArrayBlockingQueue(20));
新创建线程池默认有几个线程,可变吗?
新创建的线程池默认情况是没有线程数的,但是有两个预热方法。
ExecutorService executor = new ThreadPoolExecutor(10,15,60L,TimeUnit,SECONDS, new ArrayBlockingQueue(20));
int coreNum = executor.prestartAllCoreThreads(); // 返回当前创建的核心线程数
Boolean bo = executor.prestartCoreThread(); //当前核心线程是否创建成功
execute() 和 submit() 的区别
- 参数不同,execute参数是runnable类型,submit的参数可以是runnable和callable。
void execute(Runnable command);
submit(Runnable command)
submit(Callable task)
submit(Runnable task, T result); - 返回结果不同,execute()没有返回值,submit()可以有返回值,适用于需要关注返回值的场景,
线程池关闭方法,以及区别
- 调用shotdown() 方法,线程池将不会在接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务,会等当前线程执行完成后shotdown操作。 在真正shotdown前如果有任务过来会执行拒绝策略。
- 调用shotdownNow()方法,对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
初始化线程池时线程数的选择
- 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。
- 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。
上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。
线程池都有哪几种工作队列
1、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3、SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
4、PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
什么是线程池?
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
为什么要使用线程池?
创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉)
线程池有什么作用?
线程池作用就是限制系统中执行线程的数量。
-
提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
-
方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。
过了空闲时间回收线程底层代码是怎样实现的吗?
t.isInterrupted() && w.tryLock() == true;
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();
}
}