一、前置知识补充(关于线程池的使用,熟悉的同学可以直接跳过本节)
1、线程池的创建和使用:
阿里最新开发手册关于并发创建线程池时有如下强制规定:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2) CachedThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
因此我们需要从根本上避免因代码不规范而产生的OOM!
下面我们介绍如何正确的使用线程池!
使用来创建线程池,其构造函数如下(7个参数最全的那个构造方法):
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize :线程池中核心线程数的最大值;
maximumPoolSize :线程池中能拥有最多线程数;
keepAliveTime :表示空闲线程的存活时间;
unit :表示keepAliveTime的单位;
workQueue :用于缓存任务的阻塞队列;
threadFactory :指定创建线程的工厂;
handler:任务拒绝处理器;
前四个参数过于简单不再做任何解释,下面主要讲解后续三个参数!
workQueue :BlockingQueue是一个阻塞队列接口,其主要有以下三种队列实现:
1)SynchronousQueue
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。(A任务进入队列,B任务必须等A任务被移除之后才能进入队列,否则执行异常策略。你来一个我扔一个,所以说SynchronousQueue没有任何内部容量。)
2)LinkedBlockingQueue
LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
注:这个队列需要注意的是,虽然通常称其为一个无界队列,但是可以人为指定队列大小,而且由于其用于记录队列大小的参数是int类型字段,所以通常意义上的无界其实就是队列长度为 Integer.MAX_VALUE,且在不指定队列大小的情况下也会默认队列大小为 Integer.MAX_VALUE;
3)ArrayBlockingQueue
ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小(必须指定大小),当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。
区别:
LinkedBlockingQueue:可以指定大小,也可以不指定;底层是链表结构;
ArrayBlockingQueue:必须指定大小;底层是数组结构;
threadFactory:自定义线程池名称、线程名称!(方便根据名称直接定位问题!)后面代码直接演示!
handler:ThreadPoolExecutor的拒绝策略,其主要包括以下默认的4种,当然也可以自定义!
1)AbortPolicy
ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。大于最大线程数时直接抛出异常。
2)CallerRunsPolicy
CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务(即调用线程池的线程,例如main线程)。
3)DiscardPolicy
采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
4)DiscardOldestPolicy
DiscardOldestPolicy策略的作用是,当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。
线程池的执行过程大致如下:
线程池按以下行为执行任务
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,执行任务拒绝处理器(抛出异常,拒绝任务)
2、实例演示:
自定义线程池:
package com.taosunpool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义ThreadFactory
*/
public class UserThreadFactory implements ThreadFactory {
//线程组名称
private final String namePrefix;
//线程自增序号
private final AtomicInteger nextId = new AtomicInteger(1);
// 定义线程组名称,在 jstack 问题排查时,非常有帮助
UserThreadFactory(String whatFeaturOfGroup) {
namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-"; }
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0);
return thread;
}
}
Task任务类:
package com.taosunpool;
public class DemoTask implements Runnable {
private String name;
DemoTask(String name){
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("线程名称:"+ Thread.currentThread().getName()+"; "+this.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试延迟队列(使用默认的拒绝策略AbortPolicy):
1)SynchronousQueue
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.AbortPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
(1)添加: task1时,此时线程池会创建一个From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2时,由于此时task1还在执行中,且corePoolSize=1,所以会将task2任务“放入“SynchronousQueue队列中,因为此队列不具备存储功能(最大容量为1),所以会创建线程From UserThreadFactory’s DemoTestThreadPool-Worker-2执行task2;
(3)添加: task3时,此时maximumPoolSize=2,已无法在创建新的线程去执行任务,拒绝策略AbortPolicy直接抛出异常阻塞程序!
注:task1、task2执行任务是异步的!
2)LinkedBlockingQueue
a.不设置队列大小
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.AbortPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结如下:
结果分析:
(此时程序已被终止!)
可以看到程序在不断的向队列中添加新的任务,这是非常危险的!最终的结果将会导致OOM的发生!
b.设置固定的队列大小为5
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.AbortPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,此时corePoolSize=1,由于核心线程数已满,因此会将task2放入队列中,因此队列输出:列表:task2;
(3)添加: task3,由于核心线程数已满,同样会将其放入队列中,因此输出:列表:task2 列表:task3 ;
…
(4)添加: task7,由于此时队列已满(task2,task3,task4,task5,task6),因此会创建新的线程From UserThreadFactory’s DemoTestThreadPool-Worker-2来执行task7;
(5)添加: task8,由于此时最大线程数,和队列都满载了,所以策略模式直接抛出异常不再接受新的task,但线程池中已有线程会将队列中的任务执行完成。
3)ArrayBlockingQueue(有界队列)
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.AbortPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
结果分析同上面一样,再次就不重复!
测试拒绝策略:
1)AbortPolicy
上面队列已经演示!
2)CallerRunsPolicy
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.CallerRunsPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,此时corePoolSize=1,由于核心线程数已满,因此会将task2放入队列中,因此队列输出:列表:task2;
(3)添加: task3,由于核心线程数已满,同样会将其放入队列中,因此输出:列表:task2 列表:task3 ;
…
(4)添加: task5,由于此时队列已满,因此创建新的线程From UserThreadFactory’s DemoTestThreadPool-Worker-2执行task5,输出:列表:task2 列表:task3 列表:task4 ;
(5)添加: task6,此时最大线程数、队列数都已经满载,因此策略模式触发,“线程名称:main; task6“,即主线程main执行了task6,输出: 列表:task3 列表:task4 ;
注:看下面的打印结果
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task2,说明此时执行task1的线程已经完成任务,并从队列中取出task2执行;
后续结果就是一个反复执行的过程!此策略模式就是在线程、队列满载时会调用启动线程池的主线程来执行任务,这个策略的缺点就是可能会阻塞主线程。任务不会被抛弃但会阻塞主线程!
3)DiscardPolicy
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.DiscardPolicy());
int i = 0;
for (;;){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,task3,task4时都会被放进队列中,由于程序执行太快此过程无法捕获到哈哈!
(3)注意看图中:
task5任务被From UserThreadFactory’s DemoTestThreadPool-Worker-2线程执行时,此时队列中的任务是:task2,task3,task4,此时的执行流程和上面分析的过程都一致!即核心线程–》队列—》最大线程 这个过程!
但注意看此时的任务添加数“添加: task64201“,任务已经添加到了64201个,在这个过程中,task1、task5正在被执行,task2,task3,task4在队列中,其他的任务已经被策略模式全部丢弃!
4)DiscardOldestPolicy
package com.taosunpool;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class DemoThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,
2,
3L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new UserThreadFactory("DemoTestThreadPool"),
new ThreadPoolExecutor.DiscardOldestPolicy());
int i = 0;
for (int j=0;j<10;j++){
++i;
System.out.println("添加: task"+i);
executor.execute(new DemoTask("task"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
System.out.println("列表:"+((DemoTask)iterator.next()).getName());
}
}
}
}
结果如下:
结果分析:
(1)由于前期执行都一样,所以直接从队列满载分析
(2)添加: task5时,线程池会创建From UserThreadFactory’s DemoTestThreadPool-Worker-2来执行task5,此时队列中存储信息为:
列表:task2,列表:task3,列表:task4;
(3)添加: task6,注意看此时队列的输出结果:列表:task3,列表:task4,列表:task6;
(4)添加: task7,注意看此时队列的输出结果:列表:task4,列表:task6,列表:task7;
(5)添加: task8,注意看此时队列的输出结果:列表:task6,列表:task7,列表:task8
(6)添加: task9,注意看此时队列的输出结果:列表:task7,列表:task8,列表:task9;
(7)添加: task10,注意看此时队列的输出结果:列表:task8,列表:task9,列表:task10;
由以上结果可以看出,每次添加新任务时,都会将队列中最小的任务,即最老的任务,移除!然后添加新的任务进入队列中!
由,
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task1
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-2; task5
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task8
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-2; task9
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task10
这些打印结果得出是被移除了,并未被执行!
总结:
(1)我们可以自定义线程组、线程名称(方便定位问题);
(2)线程池队列形式有三种:
SynchronousQueue:只能“暂存”一个数据,且存入数据后必须立刻要有对应的线程来消费,否则会出错,因此不能设置最大线程数,必须无界;
LinkedBlockingQueue:无界队列(不指定大小时),也可指定大小变成有界队列;
ArrayBlockingQueue:有界队列,必须指定大小;
(3)4种策略模式:
AbortPolicy:线程、队列满载后,再有新任务进入直接抛异常
CallerRunsPolicy:线程、队列满载后,再有新任务进入会调用主线程来执行任务,后续再有任务进来,若没有空余线程则会进入阻塞状态!这个策略的缺点就是可能会阻塞主线程
DiscardPolicy:线程、队列满载后,再有新任务进入则直接抛弃这些任务!
DiscardOldestPolicy:线程、队列满载后,再有新任务进入则移除满载队列中最老的任务,将新任务插入!