一、ThreadPoolExecutor类
ThreadPoolExecutor是一个线程池的执行类
其相关的参数为:
//第一个参数:核心线程的个数
int corePoolSize = 3;
//第二个参数 最大线程个数
int maximumPoolSize = 10;
//第三个参数 线程的存活时间(一般是非核心线程的可存活时间, 但是如果设置了allowCoreThreadTimeOut为true 也会影响核心线程)
long keepAliveTime = 10L; //非核心线程的存活时间
//第四个参数 指定第三个时间参数的单位
TimeUnit unit = TimeUnit.SECONDS;
//第五个参数 线程池中的任务队列(用于保存未消耗的线程对象)
ThreadPoolExecutor常见的操作主要有以下几个方法:
- getPoolSize():返回线程池实际的线程数。
- getActiveCount():返回在执行者中正在执行任务的线程数。
- getCompletedTaskCount():返回执行者完成的任务数。
- submit(): 提交一个线程给线程执行者,如果执行者有空余线程,则直接执行;否则等待直到有空闲线程。这里调用sumbit后,并不会阻塞调用线程。调用者所在的线程和执行的线程并发运行。
- execute(runbale)加入并执行相关的线程
- shutdown(): 调用这个方法后,线程执行者在完成当前已经提交的所有任务后,结束运行。 a. 在主线程中如果调用线程执行者的这个方法,并不会使线程执行者中已经submit的任务中断(无论是待执行、执行中) b. 调用shutdown会通知执行者,后面提交的任务“不允许接受”,在shutdown后提交任务会抛RejectedExecutionException 的异常信息。
- shutdownNow(): 调用这个方法后,立即停止执行者的运行。返回待执行的Task。 a. 中断所有正在执行的线程: 正在执行的线程会收到中断信息。抛出InterruptedException b. 后面提交的任务“不允许接受”,在shutdownNow后再调用submit提交任务,会抛出RejectedExecutionException的异常息。
二、ThreadPoolExecutor的使用
1、当任务线程数量<=核心线程数时 则每一个任务线程都会被核心线程执行,不会新建线程执行,队列中没有任务 无论任务队列采用哪种方式实现
2、当任务线程数>核心线程,且<=最大线程数 分三种情况
2.1 使用LinkedBlockingQueue队列时(自动扩容,没有大小区分) 则会将超过核心线程的线程任务先放置到队列中,等到核心线程有空闲的时候会从队列中获取任务。
2.2 使用SynchronousQueue时队列是,则会创建相同数量的线程和核心线程(新创建的线程+核心线程=任务线程 任务线程不大于线程池的最大线程数)一起去执行线程任务 SynchronousQueue内部并没有数据缓存空间
详细解释请参考:http://ifeve.com/java-synchronousqueue/
2.3 使用ArrayBlockingQueue队列(可以显示的设置队列长度)
长度不限 通过核心线程去消费线程任务,剩余的线程任务放置到队列中且最终由核心线程消费。
//运行的线程数大于核心线程 但是小于最大线程数的三种队列的不同处理形式
@Test
public void testOne(){
//第一个参数:核心线程的个数
int corePoolSize = 3;
//第二个参数 最大线程个数
int maximumPoolSize = 10;
//第三个参数 线程的存活时间(一般是非核心线程的可存活时间, 但是如果设置了allowCoreThreadTimeOut为true 也会影响核心线程)
long keepAliveTime = 10L; //非核心线程的存活时间
//第四个参数 指定第三个时间参数的单位
TimeUnit unit = TimeUnit.SECONDS;
//第五个参数 线程池中的任务队列.常用的有三种队列,
// 1、SynchronousQueue, 如果要运行的线程数大于核心线程数没有超过最大线程
// 则创建新的线程数来执行多余的线程,
// 即此处如果核心线程3 运行线程为6个,
// 最大线程10个 在该队列模式下 因为运行的线程数小于最大线程10 所以会开启新的3个线程来处理
//所以队列中没有未消费的线程
//BlockingQueue<Runnable> workQueue = new SynchronousQueue<Runnable>();
//2、LinkedBlockingQueue 如果要运行的线程数大于核心线程数没有超过最大线程
// 会将多于核心线程数的线程存放到队列中,
//所以的线程都通过核心线程消费
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
//3、ArrayBlockingQueue 和LinkedBlockingDeque 一样 都可以用来存储多余的线程任务
// 区别在于ArrayBlockQueue可以设置容量大小,
// 而LinkedBlockingQueue 容量无限
//BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>();
ThreadPoolExecutor executor = new ThreadPoolExecutor
(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("---先开三个---");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("---再开三个后的线程池中的情况---");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----8秒之后----");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
//超过核心线程的存活时间 本测试设置的为10s
//这里需要设置的休眠时间至少4秒以上 线程的运行时间为2秒
// 之前主线程暂停了8秒 去除执行线程的2秒(每个线程)
//则线程池中的非核心线程已经空闲了6秒 所以还需要空闲时间为4秒
// 这些线程池中的非核心线程就会被销毁
try {
Thread.sleep(4100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----再过5秒之后----");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
}
3、当前任务线程数>最大线程数
3.1 使用LinkedBlockingQueue队列时(自动扩容,没有大小区分) 则会将超过核心线程的线程任务先放置到队列中,等到核心线程有空闲的时候会从队列中获取任务。
3.2 使用SynchronousQueue时队列 因为其中不存储缓存队列信息,所有当任务数大于最大线程数时,会报错(线程池中已经有了最大的线程数了,但是还是有其他线程没法执行)
3.3 ArrayBlockingQueue队列(限制其大小)
核心线程会消费与其数量相同的线程任务,而其他的线程任务会先放置到队列中,如果队列不足则会创建新的线程去消费剩余的任务线程。
如果创建线程数量和核心线程之要大于最大线程数量则会报错。例如 队列长度为1,有8个线程任务,3个核心线程消耗3个线程任务,将一个线程任务放置到队列中,此外还需要
创建4个线程去消耗剩余的线程任务,但是如果新建4个线程的话,线程池中总共会出现7个线程大于最大线程数,会报错。
@Test
public void testTwo(){
//当任务线程数大于最大线程数
// 使用LinkedBlockingQueue队列时(自动扩容,没有大小区分)
// 则会将超过核心线程的线程任务先放置到队列中,等到核心线程有空闲的时候会从队列中获取任务。
//BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
//使用SynchronousQueue时队列 因为其中不存储缓存队列信息,所有当任务数大于最大线程数时,
// 会报错(线程池中已经有了最大的线程数了,但是还是有其他线程没法执行)
// ArrayBlockingQueue队列(限制其大小)
//核心线程会消费与其数量相同的线程任务,而其他的线程任务会先放置到队列中,
// 如果队列不足则会创建新的线程去消费剩余的任务线程。
// 如过创建的线程总数和总容量还是小于线程任务数,则会报错
//BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
//设置队列数大于最大任务线程数 模拟长度无限制
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(3);
ThreadPoolExecutor executor = new ThreadPoolExecutor
(3, 6,
100, TimeUnit.SECONDS, queue);
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
//执行线程
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("任务队列的大小:"+queue.size());
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务队列的大小:"+queue.size());
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
}
* ThreadPoolExecutor的线程拒绝策略
* 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
handler有四个选择:
策略1、ThreadPoolExecutor.AbortPolicy()
抛出java.util.concurrent.RejectedExecutionException异常
策略2:ThreadPoolExecutor.CallerRunsPolicy
用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务(启动该线程的调用方法中);如果执行程序已关闭,则会丢弃该任务
策略3: DiscardOldestPolicy
从队列中删除一个线程任务,然后将剩余的线程任务添加进入线程池中
策略4:ThreadPoolExecutor.DiscardPolicy
用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务
测试用例代码:
public class ThreadPoolTest {
private Runnable myRunnable = null;
@Before
public void initRunnable() {
myRunnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
@Test
public void testOne() {
//第一个参数:核心线程的个数
int corePoolSize = 3;
//第二个参数 最大线程个数
int maximumPoolSize = 10;
//第三个参数 线程的存活时间(一般是非核心线程的可存活时间, 但是如果设置了allowCoreThreadTimeOut为true 也会影响核心线程)
long keepAliveTime = 100L;
//第四个参数 指定第三个时间参数的单位
TimeUnit unit = TimeUnit.SECONDS;
//第五个参数 线程池中的任务队列.常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue
SynchronousQueue<Runnable> workQueue = new SynchronousQueue<Runnable>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("---先开三个---");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
/*
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
*/
System.out.println("---再开三个---");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----8秒之后----");
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
}
@Test
public void testTwo() {
//BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
//BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(6); //设置队列数大于最大任务线程数 模拟长度无限制
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 100, TimeUnit.SECONDS, queue);
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
//任务线程数6个
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("任务队列的大小:" + queue.size());
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务队列的大小:" + queue.size());
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
}
@Test
public void testThree() {
//BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
//BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(3);
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 100, TimeUnit.SECONDS, queue);
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
//线程任务数9个
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务队列的大小:" + queue.size());
System.out.println("核心线程数" + executor.getCorePoolSize());
System.out.println("线程池数" + executor.getPoolSize());
System.out.println("队列任务数" + executor.getQueue().size());
}
}
二、线程池的停止操作
1、shutdown()方法
阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完
后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。
具体的流程是:
通过将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的
2、shutdownNow()方法
阻止新来的任务提交,同时会中断当前正在运行的线程,即workers中的线程。另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。通过将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。
3、awaitTermination(long timeout,TimeUnit unit)
awaitTermination会一直等待,直到线程池状态为TERMINATED或者,等待的时间到达了指定的时间。
三、Executors工具类获取线程池
在阿里开发手册中当我们使用Executors创建线程池不被推荐,反而建议我们使用ThreadPoolExecutor来创建 这个分析一下原因
@Test
public void testNewFixedPool() throws InterruptedException {
/**
* newCachedThreadPool 创建一个可缓存的线程池工厂,对于新的任务,先去查看当前的缓存池中是否有空闲的线程
* 如果有则直接使用线程,否则新建一个线程,线程池中空闲线程闲置超过60秒未被使用则该线程被移除销毁(本质上不会一直保留线程)
* 其实内部使用的new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
* 结合上面所述,则可以明白 创建的线程工厂 核心线程为0 可允许的最大线程为Integer.MAX_VALUE(如果使用Executors 默认最大线程 在极端情况下
* 会特别的大 导致OOM 所有阿里开发手册不建议使用)
*/
//ExecutorService executorService = Executors.newCachedThreadPool();
/**
* newFixedThreadPool 创建一个有固定线程数的线程池,对于新的任务,如果固定线程中的所有线程有空闲线程,则使用空闲线程执行任务
* 没有则放入LinkedBlockingQueue 的基于链表的队列中,但是创建的队列是无限大的,可能会造成队列中的任务无限大,造成内存溢出
* 其内部是使用new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
* 核心线程和最大线程是一致且固定,线程池中始终保持固定的线程数量
*/
//executorService = Executors.newFixedThreadPool(10);
/**
* newScheduledThreadPool创建一个可以周期性执行线程的线程池服务,该类底层调用
* new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
* 所以会出现线程池中线程无限大 导致OOM(内存溢出)
*/
CountDownLatch countDownLatch = new CountDownLatch(1);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
countDownLatch.countDown();
}
}, 3, TimeUnit.SECONDS);
countDownLatch.await();
/**
* 单线程化的线程池 即底层使用 (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
* 核心线程以及最大线程都是1 多余的任务则使用队列来保存,保证每次请求都使用一个线程
* (同newFixedThreadPool 极端情况队列中保存大量任务 导致OOM)
*/
Executors.newSingleThreadExecutor();
/**
* 单线程化的定时执行的线程池 类似于Executors.newScheduledThreadPool(1);
* 当然可能存在的问题与newScheduledThreadPool的问题一致
*/
Executors.newSingleThreadScheduledExecutor();
}