是什么?
可以理解为一个泳池,里面装着创建好的线程
为什么使用?
我们执行多线程任务的时候,任意创建和销毁线程,会消耗大量的系统资源,线程之间的切换,线程的数量,加锁和释放锁操作,都会造成资源的严重消耗,甚至导致系统程序崩溃,所以为了管理这种杂乱无章的场面,我们就需要有一个工具来管理线程执行任务,于是就有了线程池。
优缺点
优点
(1)减低了频繁开启关闭线程的资源消耗。
(2)通过复用以及创建好的空闲状态线程,提高了线程的使用率,提高了系统
响应速度
(3)通过控制线程开启的数量,保证内存占用率的健康状态,减少了CPU切换
和恢复线程执行任务的成本。
(4)可通过缓冲队列等实现更灵活的线程实现,自定义线程池等,防止出现
OOM(Out Of Memory)
缺点
(1)死锁:涉及到多线程都会有的死锁的可能(A有1,要2;B有2,要3;C有3,要1,僵持不下)。例如:池中的大部分线程都在等待一个等待队列里面的执行任务,但是等待队列里的那个任务又因为没有可执行任务的空闲线程而一直等待,所以就会造成线程池独有的死锁。
(2)内存资源不足/性能下降:每条线程执行任务都会需要一定的内存空间,需要堆栈内存,如果不能合理调整控制线程池的大小,容易造成系统资源的消耗,线程之间切换的开销和加锁释放锁的开销,也会严重影响程序的性能。
(3)线程泄漏:有任务进来的时候,会分配一条线程去执行,但是当执行任务时候发生一些未捕获的Exception,导致线程无法正确执行完目标任务进而无法返还线程到线程池中,甚至有的线程任务是定时执行或者触发执行或者等待用户输入之类的,一直没有达到执行任务的条件,于是一直在等待,这样多次反复,就造成线程池中没有闲置的线程,但是又没办法执行任务,一直卡着。
(4**)请求过载**:没有做好压力测试,不知道服务器端能扛住多大的请求压力,开启多线程跑任务的时候任务量太大,大量请求冲溃了服务器。
如何执行
简单来说
创建线程
1、通过Executors
直接创建线程,不推荐,推荐文章:从EXECUTORS和THREADPOOLEXECUTOR的区别分析到线程池的OOM
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
2、ThreadPoolExecutor
创建线程
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(10,20,10, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
-
corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
-
maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
-
keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
-
workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
-
threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
-
handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
workQueue(任务队列)是拿来干什么的,有哪几种(7种)
线程的工作队列用来存放等待执行的线程,有以下几种常用的队列(都是线程安全):
-
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列,先进先出原则
-
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,先进先出,吞吐量高于数组阻塞队列
-
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。,按照优先级对元素进行排序(无界就是maximumPoolSizes设置为无界)
-
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
-
SynchronousQueue:不存储元素的无界阻塞队列,任务插入操作等待,有空闲的线程调用或者创建新线程时候移除操作,否则插入操作一直阻塞,吞吐量高于链表阻塞队列,不属于一种容器队列,只是类似一种任务分发
-
LinkedTransferQueue:由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了
tryTransfer
和transfer
方法.transfer方法
如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。
tryTransfer方法
用来试探生产者传入的元素能否直接传给消费者。,如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。
-
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。
有界队列:就是有固定大小的队列。比如设定了固定大小的 LinkedBlockingQueue,又或者大小为 0,只是在生产者和消费者中做中转用的 SynchronousQueue。
无界队列:指的是没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出。当然现实几乎不会有到这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的体验上,就相当于 “无界”。比如没有设定固定大小的 LinkedBlockingQueue。
通俗讲就是说设置了容量,就是有界队列;没设置容量,系统自动匹配最大值Integer.MAX_VALUE,就是无界队列。
有界无界的区别就是:有界队列有长度限制,无界队列没有长度限制。
这些队列的常用方法
方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
(handler)饱和拒绝策略有四种
(1)ThreadPoolExecutor.AbortPolicy
(默认)直接拒绝,抛出RejectedExecutionException
(2)ThreadPoolExecutor.CallerRunsPolicy
:使用调用者的线程执行任务,不抛出异常不抛弃任务。简单来说就是 将任务返还给调用主线程去执行,趁这段时间处理线程池正在执行的任务
(3)ThreadPoolExecutor.DiscardOldestPolicy
:放弃队列中等待最久的任务(注意不能跟优先级
队列组合使用,不然会把优先级最高的最老的任务抛弃)
(4)ThreadPoolExecutor.DiscardPolicy
:丢弃在队列中队首的任务
线程池
1、newFixedThreadPool()
由于使用了LinkedBlockingQueue
所以maximumPoolSize
没用,当corePoolSize
满了之后就加入到LinkedBlockingQueue
队列中。
每当某个线程执行完成之后就从LinkedBlockingQueue
队列中取一个。
所以这个是创建固定大小的线程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
2、newSingleThreadPool()
创建线程数为1的线程池,由于使用了LinkedBlockingQueue
所以maximumPoolSize
没用,corePoolSize
为1表示线程数大小为1,满了就放入队列中,执行完了就从队列取一个。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
3.newCachedThreadPool()
创建可缓冲的线程池。没有大小限制。由于corePoolSize
为0所以任务会放入SynchronousQueue
队列中,SynchronousQueue
只能存放大小为1,所以会立刻新起线程,由于maxumumPoolSize
为Integer.MAX_VALUE
所以可以认为大小为2147483647
。受内存大小限制。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
4、newScheduledThreadPool()
定长定时线程池,在定长线程池的基础上带有延时执行任务和定时周期性执行任务的功能
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
上述线程池比较
工程方法 | corePoolSize | maxmumPoolSize | keepAliveTime | workQueue |
---|---|---|---|---|
newCachedThreadPool | 0 | Integer.MAX_VALUE | 60s | SynchronousQueue |
newFixedThreadPool | nThreads | nThreads | 0 | LinkedBlockingQueue |
newSingleThreadExecutor | 1 | 1 | 0 | LinkedBlockingQueue |
newScheduledThreadPoolcorePoolSize | corePoolSize | Integer.MAX_VALUE | Integer.MAX_VALUEInteger.MAX_VALUE0 | DelayedWorkQueue |