1 线程池的优势
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
- 提供更强大的功能,延时定时线程池。
2 线程池原理
其中最核心的思想就是把宝贵的资源放到一个池子中;每次使用都从里面获取,用完之后又放回池子供其他人使用,达到重复利用的目的。
其实java线程池的实现原理很简单,就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先检测线程核心数是否已满,满了再将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
3 创建线程池的4种方式
- Executors.newSingleThreadExecutor():创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- Executors.newFixedThreadPool(nThreads):创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;只存在核心线程的,对于核心线程来说,如果我们不设置allowCoreThreadTimeOut属性的话是不存在超时淘汰机制的,这类线程池中的corePoolSize的大小是等于maximumPoolSize大小的,如果线程池中的线程都处于活动状态的话,如果有新任务到来,他是不会开辟新的工作线程来处理这些任务的,只能将这些任务放到阻塞队列里面进行等到,直到有核心线程空闲为止。
- Executors.newCachedThreadPool():创建一个可缓存的线程池。该类线程池中线程的数量是不确定的,理论上可以达到Integer.MAX_VALUE个,这种线程池中的线程都是非核心线程,既然是非核心线程,那么就存在超时淘汰机制了,当里面的某个线程空闲时间超过了设定的超时时间的话,就会回收掉该线程
- Executors.newScheduledThreadPool(int corePoolSize):创建一个大小无限的线程池。核心线程的数量是固定的,非核心线程的数量是不限制的,同时对于非核心线程是存在超时淘汰机制的,此线程池支持定时以及周期性执行任务的需求。
实现代码:
//newSingleThreadExecutor
public void Test1 () {
//1.创建单线程
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
// newSingleThreadExecutor.execute(new Runnable() {
// @Override
// public void run() {
// System.out.println("index:" + index);
// try {
// Thread.sleep(200);
// } catch (Exception e) {
// // TODO: handle exception
// }
// }
// });
newSingleThreadExecutor.execute(()->{
System.out.println("index:" + index);
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
});
}
newSingleThreadExecutor.shutdown();
}
//newFixedThreadPool
public void Test2() {
//1.创建可固定长度的线程池
ExecutorService newExecutorService = Executors.newFixedThreadPool(3);
//创建了10个线程
for (int i = 0; i < 10; i++) {
int temp = i;
newExecutorService.execute(() -> {
System.out.println("threadName;" + Thread.currentThread().getName() + ",i" + temp);
});
}
}
//newCachedThreadPool
public void Test3() {
//1.创建可缓存的线程池,可重复利用
ExecutorService newExecutorService = Executors.newCachedThreadPool();
//创建了10个线程
for (int i = 0; i < 10; i++) {
int temp = i;
newExecutorService.execute(() -> {
System.out.println("threadName;" + Thread.currentThread().getName() + ",i" + temp);
});
}
}
//newScheduledThreadPool
public void Test4() {
//1.创建可定时线程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
// newScheduledThreadPool.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("i:" + temp);
// }
// }, 3, TimeUnit.SECONDS);//延迟时间
newScheduledThreadPool.schedule(() -> {
System.out.println("i:" + temp);
}, 3, TimeUnit.SECONDS);
}
}
源码分析:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 本质ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handle // 拒绝策略) {
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;
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
//ScheduledThreadPoolExecutor继承了ThreadPoolExecutor类,最终还是调用ThreadPoolExecutor()
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
4 Executor框架
Executor框架是java中的线程池实现。Executor是最顶层的接口定义,它的子类和实现主要包括ExecutorService,ScheduledExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor,ForkJoinPool等。其结构如下图所示
Executor:Executor是一个接口,其只定义了一个execute()方法:void execute(Runnable command);
只能提交Runnable形式的任务,不支持提交Callable带有返回值的任务。
ExecutorService:ExecutorService在Executor的基础上加入了线程池的生命周期管理,可以通过ExecutorService#shutdown或者ExecutorService#shutdownNow方法来关闭我们的线程池。ExecutorService支持提交Callable形式的任务,提交完Callable任务后我们拿到一个Future,它代表一个异步任务执行的结果。关于shutdown和shutdownNow方法我们需要注意的是:这两个方法是非阻塞的,调用后立即返回,不会等待线程池关闭完成。如果我们需要等待线程池处理完成再返回可以使用ExecutorService#awaitTermination来完成。shutdown方法会等待线程池中已经运行的任何和阻塞队列中等待执行的任务执行完成,而shutdownNow则不会,shutdownNow方法会尝试中断线程池中已经运行的任务,阻塞队列中等待的任务不会再被执行,阻塞队列中等待执行的任务会作为返回值返回。
ThreadPoolExecutor:是线程池中最核心的类,这里着重说一下这个类的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(线程池基本大小):线程池的核心线程数目。当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
- maximumPoolSize(线程池最大大小):线程池的最大线程数目,当线程池数量已经等于corePoolSize并且阻塞队列也已经满了,则看线程数量是否小于maximumPoolSize:如果小于则创建一个线程来处理请求,否则使用“饱和策略”来拒绝这个请求。对于大于corePoolSize部分的线程,称作这部分线程为“idle threads”,这部分线程会有一个最大空闲时间,如果超过这个空闲时间还没有任务进来则将这些空闲线程回收。
- keepAliveTime(线程存活保持时间):这两个参数主要用来控制idle threads的最大空闲时间,超过这个空闲时间空闲线程将被回收。ThreadPoolExecutor中有一个属性:
private volatile boolean allowCoreThreadTimeOut;
这个用来指定是否允许核心线程空闲超时回收,默认为false,即不允许核心线程超时回收,核心线程将一直等待新任务。如果设置这个参数为true,核心线程空闲超时后也可以被回收。 - unit:keepAliveTime的时间单位。
- workQueue(阻塞队列):阻塞队列,超过corePoolSize部分的请求放入这个阻塞队列中等待执行。阻塞队列分为有界阻塞队列和无界阻塞队列。在创建阻塞队列时如果我们指定了这个队列的“capacity”则这个队列就是有界的,否则是无界的。这里有一点需要注意:使用线程池之前请明确是否真的需要无界阻塞队列,如果阻塞队列是无界的,会导致大量的请求堆积,进而造成内存溢出系统崩溃。
- threadFactory(线程工厂):是一个线程池工厂,主要用来为线程池创建线程,我们可以定制一个ThreadFactory来达到统一命名我们线程池中的线程的目的。
- handler(线程饱和策略):饱和策略,用来拒绝多余的请求。
4种拒绝策略:
- AbortPolicy(默认):表示无法处理新任务,抛异常;
- CallerRunsPolicy:等待调用者线程空闲后执行这个任务,调用者线程:假设在线程A中创建线程池,线程A就是调用者线程;
- DiscardOldestPolicy:丢弃阻塞队列中最近的一任务,将新的任务加入队尾;
- DiscardPolicy:不处理并丢弃新任务,也不报错。
//拒绝策略都实现了RejectedExecutionHandler接口
//自定义拒绝策略,也需要实现RejectedExecutionHandler
public class MyRejectionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + "被拒绝了");
}
}
ThreadPoolExecutor中的几个关键属性:
//这个属性是用来存放 当前运行的worker数量以及线程池状态的
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
补充:JDK7提供了7个阻塞队列,分别是
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
分析: newSingleThreadExecutor和newFixedThreadPool使用的是有界队列LinkedBlockingQueue,但是容量大小默认Integer.MAX_VALUE
newCachedThreadPool和newScheduledThreadPool最大线程数都是Integer.MAX_VALUE
5 线程池流程
- 判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则往后。
- 判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则往后。
- 判断整个线程池是否已满(是否超过最大连接数),没满则创建一个新的工作线程来执行新的任务(注意:新的任务进来不会放入队列中,此时会优先执行新的任务),已满则执行饱和策略。
6 线程池的关闭,shutdown()和shutdownNow()
线程池的关闭用到两个方法,shutdown和shutdownNow,都是位于ThreadPoolExecutor里面的
shutdown:它会将线程池状态切换成Shutdown,此时是不会影响对阻塞队列中任务执行的,会将剩余的任务全部执行完,但是会拒绝执行新加进来的任务,同时会回收闲置的Worker;
shutdownNow:先将线程池状态设置为STOP,然后拒绝所有提交的任务。最后中断左右正在运行中的worker,同时会回收所有的Worker;
7 execute()和submit()方法
- execute(),执行一个任务,没有返回值。
- submit(),提交一个线程任务,有返回值。
submit(Callable<T> task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。
submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。
Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。
8 核心线程数设置
(1)cpu(计算)密集型任务:
cpu使用率较高(也就是一些复杂运算,逻辑处理),线程数一般只需要cpu核数的线程就可以了。 这一类型的在开发中多出现的一些业务复杂计算和逻辑处理过程中。一般简单设置为:cpu核数+1
(2)I/O密集型任务:
cpu使用率较低,程序中会存在大量I/O操作(文件读写、DB读写、网络请求等)占据时间,导致线程空余时间出来,所以通常就需要开cpu核数的两倍的线程, 当线程进行I/O操作cpu空暇时启用其他线程继续使用cpu,提高cpu使用率。一般简单设置为:2*cpu核数
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。这一类型在开发中主要出现在一些读写操作频繁的业务逻辑中。