一、概述
为了避免频繁地创建和销毁线程,我们可以让创建的线程进行复用。线程池中,总有那么几个活跃线程,当你需要使用线程时,可以从池子中随便拿一个空闲线程,当完成工作时,并不急着关闭线程,而是将这个线程退回到池子,方便其他人使用。简言之,在使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向池子归还线程。如下图所示
二、JDK对线程池的支持
为了更好地控制更多线程,JDK提供了一套Executor框架帮助开发人员有效进行线程控制,本质就是一个线程池,核心成员如下
以上成员均在JUC(java.util.concurrent)包中,其中ThreadPoolExecutor表示一个线程池。Executors类则扮演着线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池,Executors框架提供的工厂方法如下:
newFixedThreadPool(int nThread):该方法返回一个固定数量的线程池。线程池中的线程数量始终不变。当有新任务提交时,若线程池中有空闲线程,则立即执行。若没有,则新的任务会被暂存在任务队列里,待有空闲线程时,便处理在任务队列中的任务。
newSingleThreadExecutor():该方法返回一个只有一个线程的线程池。若多于1个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先进先出的顺序执行该队列中的任务。
newCachedThreadPool():该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定。但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
newSingleThreadScheduledExecutor():该方法返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService接口在ExecutorService接口之上扩展了在给定时间执行某任务的功能,如果某个固定的延时之后执行,或者周期性执行某个任务。
newScheduledThreadPool(int corePoolSize):该方法也返回一个ScheduledExecutorService对象,但该线程池可以指定线程数量。
三、线程池的使用
一池5线程
import java.util.Random;
import java.util.concurrent.*;
/**
* @date: 2018/9/13
* @description:
*/
public class ThreadPoolTest {
public static void main(String[] args) {
PoolTest();
}
private static void PoolTest() {
ExecutorService service = Executors.newFixedThreadPool(5);//一池5线程
// ExecutorService service = Executors.newSingleThreadExecutor();//一池1线程
// ExecutorService service = Executors.newCachedThreadPool();//线程池根据任务需求自行new出适量线程
try{
Future result = null;
for(int i = 0; i < 500; i++){
result = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(500);
System.out.print(Thread.currentThread().getName() + "\t");
return new Random().nextInt(10) * new Random().nextInt(5);
}
});
System.out.println(result.get());
}
}catch(Exception e){
e.printStackTrace();
}finally {
service.shutdown();
}
}
}
运行结果:
总共5个线程执行任务
一池一线程
import java.util.Random;
import java.util.concurrent.*;
/**
* @date: 2018/9/13
* @description:
*/
public class ThreadPoolTest {
public static void main(String[] args) {
PoolTest();
}
private static void PoolTest() {
// ExecutorService service = Executors.newFixedThreadPool(5);//一池5线程
ExecutorService service = Executors.newSingleThreadExecutor();//一池1线程
// ExecutorService service = Executors.newCachedThreadPool();//线程池根据任务需求自行new出适量线程
try{
Future result = null;
for(int i = 0; i < 500; i++){
result = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(500);
System.out.print(Thread.currentThread().getName() + "\t");
return new Random().nextInt(10) * new Random().nextInt(5);
}
});
System.out.println(result.get());
}
}catch(Exception e){
e.printStackTrace();
}finally {
service.shutdown();
}
}
}
运行结果:
总共有一个线程执行任务
线程池根据任务需求自行new出适量线程
import java.util.Random;
import java.util.concurrent.*;
/**
* @author zhanhaozhi
* @date: 2018/9/13
* @description:
*/
public class ThreadPoolTest {
public static void main(String[] args) {
PoolTest();
}
private static void PoolTest() {
// ExecutorService service = Executors.newFixedThreadPool(5);//一池5线程
//ExecutorService service = Executors.newSingleThreadExecutor();//一池1线程
ExecutorService service = Executors.newCachedThreadPool();//线程池根据任务需求自行new出适量线程
try{
Future result = null;
for(int i = 0; i < 500; i++){
result = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(500);
System.out.print(Thread.currentThread().getName() + "\t");
return new Random().nextInt(10) * new Random().nextInt(5);
}
});
System.out.println(result.get());
}
}catch(Exception e){
e.printStackTrace();
}finally {
service.shutdown();
}
}
}
运行结果:
2个线程执行任务
调度地执行任务
import java.util.Random;
import java.util.concurrent.*;
/**
* @date: 2018/9/13
* @description:
*/
public class ThreadPoolTest {
public static void main(String[] args) {
// PoolTest();
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
ScheduledFuture<Integer> result = null;
try{
for(int i = 1; i < 200; i++){
result = service.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(200);
System.out.print(Thread.currentThread().getName() + "\t");
return new Random().nextInt(10);
}
}, 3, TimeUnit.SECONDS);
System.out.println("------" + result.get());
}
}catch(Exception e){
e.printStackTrace();
}finally {
service.shutdown();
}
}
四、核心线程池的内部实现
对于核心的几个线程池,无论是newFixedThreadPool()方法,newSingleThreadExecutor()方法,还是newCachedThreadPool()方法,虽然看起来创建的线程有着完全不同的功能特点,但其内部实现均使用了ThreadPoolExecutor实现。
其中,ThreadPoolExecutor的重要构造函数如下
其中的几个重要参数:
coolPoolSize:指定了线程池中的线程数量
maximumPoolSize:指定了线程池中最大线程数量
keepAliveTime:当线程池线程数量超过coolPoolSize时,多余的空闲线程的存活时间。也就是超过coolPoolSize的空闲线程,在多长时间内,会被销毁。
unit:keepAliveTime的单位
workQueue:任务队列,被提交的但尚未执行的任务
threadFactory:线程工厂,用于创建线程,一般用默认的即可。
handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。
参数workQueue和参数handler的说明:
-
参数workQueue指被提交单未执行的任务队列,它是一个BlockingQueue接口的对象,仅用于存放Runnable对象。根据队列功能分类,在ThreadPoolExecutor的构造函数中可以使用以下几种BlockingQueue
-
直接提交的队列:由SynchronousQueue提供。SynchronousQueue是一个特殊的BlockingQueue。SynchronousQueue是一个特殊的BlockingQueue。SynchronousQueue没有容量,每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作。如果使用SynchronousQueue,提交的任务不会被真实的保存,而总是将新任务提交给线程执行。如果没有空闲的进程??则尝试创建新的线程??如果进程数量已经达到最大值,则执行拒绝策略。因此,使用SynchronousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略。
-
有界的任务队列:使用ArrayBlockingQueue实现。ArrayBlockingQueue的构造函数必须带一个容量参数,表示该队列的最大容量。public ArrayBlockingQueue(int capacity)。当使用有界的任务队列时,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则会优先创建新的线程。若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程不大于maximumPoolSize的前提下,创建新的进程执行任务。若大于maximumPoolSize,执行拒绝策略。
-
无界的任务队列:使用LinkedBlockingQueue实现。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,线程池会生成新的线程执行任务,但当系统的线程数达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入等待队列。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。
-
优先任务队列:优秀任务队列是带有执行优先级的队列。它通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。无论是有界队列ArrayBlockingQueue,还是未指定大小的无界队列LinkedBlockingQueue都是按照先进先出算法处理任务的。而PriorityBlockingQueue则可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能有很好的质量保证。(总是确保高优先级的任务先执行)
-
-
newFixedThreadPool()方法返回了一个corePoolSize和maximumPoolSize大小一样的,并且使用了LinkedBlockingQueue任务队列的线程池。因为对于固定大小的线程池而言,不存在线程数量的动态变化,因此corePoolSize和maximumPoolSize可以相等。同时,它使用无界队列存放无法立即执行的任务,当任务提交非常频繁的时候,该队列可能迅速膨胀,从而耗尽系统资源。
-
newSingleThreadExecutor()返回的单线程线程池,是newFixedThreadPool()方法的一种退化,只是简单的将线程池线程数量设置为1。
-
newCachedThreadPool()方法返回corePoolSize为0,maximumPoolSize无穷大的线程池,这意味着在没有任务时,该线程池内无线程,而当任务被提交时,该线程池会使用空闲的线程执行任务。若无空闲线程,则将任务加入SynchronousQueue队列,而SynchronousQueue队列是一种直接提交的队列,它总会迫使线程池增加新的线程之心任务。当任务执行完毕后,由于corePoolSize为0,因此空闲线程又会在指定时间内(60秒)被回收。
-
对于newCachedThreadPool(),如果同时有大量任务被提交,而任务的执行又不那么快时,那么系统便会开启等量的线程处理,这样可能会很快耗尽系统的资源。
ThreadPoolExecutor线程池的核心调度代码
workerCountOf()函数取得了当前线程池的线程总数。当线程总数小于corePoolSize核心线程数时,会将任务通过addWorker()方法直接调度执行。否则,则通过workQueue.offer()进入等待队列。如果进入等待队列失败(比如有界队列达到了上限,或者使用了SynchronousQueue),则会将任务提交给线程池。如果当前线程数已经达到maximumPoolSize,则提交失败,就执行最后一行的拒绝策略。
ThreadPoolExecutor的任务调度逻辑
拒绝策略
-
ThreadPoolExecutor的最后一个参数(handler)指定了拒绝策略。也就是当任务数量超过系统实际承载能力时,需要使用拒绝策略。拒绝策略是系统超负荷运行时的补救措施。通常是因为线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列中也已经排满了,再也塞不下新任务了,这时就需要一套机制来处理这个问题。
-
JDK内置的拒绝策略
-
AbortPolicy:该策略会直接抛出异常,阻止系统正常工作。
-
CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。不会真的丢弃任务,但任务提交线程的性能可能急剧下降。
-
DiscardOldestPolicy:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
-
DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理。
-