线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程将排队等候,等其他线程执行完毕,再从队列中取出来执行。
主要特点:线程复用,控制最大并发数,管理线程
1、降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
2、提高相应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService ,ThreadPoolExecutor这几个类
常用的默认获取线程池
Executors.newFixedThreadPool(int):一个线程池固定线程数,执行长期的任务,性能较好
Executors.newSingleThreadExecutor():一个线程池一个线程,一个任务一个任务执行的场景
Executors.newCacheThreadPool():一个线程池多个线程数,执行很多短期异步的小程序或者负载较轻的服务器
线程池的几大参数:
Executors.newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Executors.newCacheThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从源码中发现在实例化线程池的时候,配置的参数是5个,但是其底部调用了其重载的构造方法,总共是7个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
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:线程池能够容纳同时执行的最大线程数,此值必须大于等于1。(可以类比银行办理业务总共的窗口)
keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁知道剩下corePoolSize个线程为止。(类比当银行网点人数过多,当天办理业务的窗口忙不过来,其他的窗口也被启用办理业务之后,但是随着时间的推移,业务量逐渐减少。当其他窗口的工作人员等了keepAliveTime时间后没有人来办理业务,那么就可以休息了,所以这里当成其他被启用的窗口等候一定时间仍然没有人来此办理业务)
unit:keepAliveTime的单位
workQueue:任务队列,被提交单尚未被执行的任务(可以类比当天办理业务的窗口都在办理业务,多余的人需要在候客厅等候)
threadFactory:表示生成线程洗中工作线程的线程工厂,用于创建线程,一般默认即可(银行网点的一些默认属性,如配有工作人员,有银行logo)
handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝(类比银行的接待量已经达极限,所有的窗口都在办理,候客厅也已经有很多人等候,容不下更多人,这是还有源源不断的人来办理,银行就开始拒绝再来办理业务的人)
1、在创建线程池后,等待提交过来的任务请求
2、当调用execute方法添加一个请求任务时,线程池会做如下判断
如果正在运行的此案成数量小于corePoolSize,那么马上创建线程运行这个任务
如果正在运行的此案成数量大于后等于corePoolSize,那么近这个任务放入阻塞队列
如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么会创建线程运行这个任务
如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和和拒绝策略来执行
3、当一个线程完成任务时,它会从队列中取下一个任务来执行
4、当一个线程无事可做超过一定时间(keepAliveTime)时,线程池会判断
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
如果线程池的所有任务完成后,线程池会收缩到corePoolSize的大小
在实际开发中不会使用Executors默认创建的线程池,从源码可知在初始化线程池的时候使用的阻塞队列的长度是Integer.MAX_VALUE,一般自己创建ThreadPoolExecutor。
线程池的内置拒绝策略(RejectedExecutionHandler):
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
public class ThreadPoolTest {
public static void main(String[] args) {
//定义一个初始线程为2,最大线程为5,阻塞队列为3,等待时间为1秒,决绝策略为抛出异常的线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try{
//模拟多个请求线程
for (int i = 0; i < 11; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+" 执行任务");
});
}
}
finally {
//关闭资源
threadPoolExecutor.shutdown();
}
}
}
其结果是超过线程池处理的能力之后直接抛出异常
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不抛出异常,而是将某些任务会退到调用者
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
超过线程池处理能力之后,其结果直接将超出能力的请求返回给调用者
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
其结果是将等待时间最长的请求抛弃
DiscardPolicy:直接丢弃任务,不予任何处理,也不抛出异常。如果允许任务丢失,这是最好的一种方案
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
其结果直接抛弃超过能力处理的请求
如何合理的设置线程数
1、CPU密集型:该任务需要大量的运算,而没有阻塞,CPU一直全速运行,CPU秘籍任务只有在真正的多核CPU上才可能得到加速(通过多线程)。CPU密集型任务配置尽可能少的线程数量。一般为 (CPU核数+1)个线程的线程池。
2、IO密集型:IO密集型任务线程并不是一直在执行任务,应配置尽可能多的线程,如 CPU核数*2
通过Runtime.getRuntime().availableProcessors()获取机器的核数