线程池是J.U.C中一个非常常用的组件,利于线程池技术可以有效利于线程资源,避免频繁的创建和销毁线程造成的系统开销,避免过多的创建线程耗尽系统资源,同时还可以更好对管理线程,包括关闭/中断以及状态监控等等。
线程池原理
1. 提交任务
2. 核心线程数是否已满
3. 若核心线程数已满,则将任务加入等待队列,如果没满,则创建新的线程
4. 任务队列是否已满,如果没满则将任务加入到等待队列,如果已满,则查看线程是否达到最大线程数
5. 如果没有达到最大线程数,则创建一个临时线程执行任务,如果已经达到最大线程数,则调用拒绝策略拒绝本次提交。
线程池的初始化及参数说明
初始化
下面看一下JDK中线程池是如何初始化的
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
上面是线程池的初始化方法,想理解线程池原理,就必须了解这些参数的具体含义。
参数说明
corePoolSize
该参数为线程池的核心线程数,可以简单的理解为线程池中的线程数,当线程池中的线程没有达到该数量时,每提交一个新的任务就会启动一个新的线程。
maximumPoolSize
该参数为线程池的最大线程数,当线程池中的线程数达到核心线程数,并且等待队列已满时,如果线程数没有达到最大线程数,就会再启动新的线程来处理任务,直到达到最大线程数。
核心线程可以理解为正式员工,(最大线程数-核心线程数)的线程可以理解为临时员工,当任务被处理完后,临时员工就会被清退,也就是关闭核心线程以外的临时线程。
keepAliveTime
该参数指定来临时员工(临时线程)空闲后的最大存活时间,也就是当临时线程处于空闲状态后,如果在指定的时间内没有接收到新的任务,就会被关闭。
unit
该参数指定临时线程存活的时间单位,也就是keepAliveTime对应的时间单位。
workQueue
该参数为线程池的工作队列,也可以理解为等待队列。当线程数达到核心线程数时,如果此时还有新任务被提交到线程池,那么新进任务会被放到工作队列中等待,一旦有空闲的核心线程,它就会工工作队列中获取一个任务来处理。
threadFactory
线程工厂,线程池创建线程时,不是直接new Thread()
,而是通过线程工厂来创建,这就给来客户端程序员留出来更好的扩展接口,可以通过定制的ThreadFactory
来实现不同的需求。
handler
拒绝策略,当线程数已经达到最大线程数,并且工作队列已满时,此时线程池不接收新的任务,JDK提供了以下几种拒绝策略:
- ThreadPoolExecutor.AbortPolicy:抛出
RejectedExecutionException
异常 - ThreadPoolExecutor.CallerRunsPolicy:将任务抛回给任务提交者线程处理
- ThreadPoolExecutor.DiscardOldestPolicy:抛弃等待时间最长的任务,处理新任务
ThreadPoolExecutor.DiscardPolicy:不做任务处理,直接丢弃
创建线程池
public class ThreadPoolTest {
public static void main(String[] args) {
//创建一个线程池
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(2,2, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),
new MyThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
//定义一个线程工厂,用来为线程池创建任务线程
private static class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
}
提交任务
向线程池提交任务的方式有两种
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
其中,submit方法返回一个Future<?>/Future<T>
,当通过submit提交一个Runnable时,返回一个Future
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("This is execute(Runnable)");
}
});
Future<?> future1 = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("This is submit(Runnable)");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//如果任务没有完成,则终止
if(!future1.isDone()){
future1.cancel(true);
}
Future<String> future2 = threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "This is submit(Callable)";
}
});
//获取返回值
String rs = future2.get();
关闭线程池
线程池的关闭方式有两种,分别是:
- shutdown()
- shutdownNow()
这两种方式的区别在于,shutdownNow()回立即终止正在执行的任务,同时丢弃在工作队列中等待的任务,而shutdown()则是不接收新的任务,但回等待正在执行的任务和工作队列中的任务被执行完成后再关闭线程池。
线程池监控
线程池提供了以下常用的监控指标接口
- taskCount:线程池需要执行的任务数量
- completedTaskCount:已经执行完成的任务数量
- largestPoolCount:线程池中的最大线程数
- poolSize:线程池中的当前线程数
- activeCount:正在执行任务的线程数
threadPoolExecutor.getTaskCount();
threadPoolExecutor.getCompletedTaskCount();
threadPoolExecutor.getLargestPoolSize();
threadPoolExecutor.getPoolSize();
threadPoolExecutor.getActiveCount();