0
点赞
收藏
分享

微信扫一扫

JUC.谈谈线程池


什么是线程池?

简单来说,线程池就是提前创建好若干个线程,当有任务到达时,就可以使用已创建好的线程去执行该任务,处理完任务之后线程不会立即销毁,而是放入池中,等待执行下一个任务。
打一个比方,线程池就像一个银行,银行里有几个常驻的窗口来处理顾客的业务,办完一个业务就进行下一个,没有任务就待命。

为什么要用线程池?

合理由的使用线程池,可以带来以下3个好处:
1.降低资源消耗:线程池可以通过复用已创建的线程来降低线程创建和销毁开销!不必一个任务就创建一个线程!
2.提高响应速度:当线程池中有空闲线程时,任务到达就可以直接执行,而不用创建线程!
3.提高线程的可管理性:线程是稀缺资源,无限制的创建线程不仅会造成资源的大量消耗,而且会降低系统的稳定性。有了线程池后就可以对线程进行统一的分配,调优和监控!

如何使用线程池?

Java中的线程池是通过Executor框架实现的,该框架中用到Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类.

JUC.谈谈线程池_创建线程


JUC.谈谈线程池_创建线程_02

常见的线程池

1.FixedThreadPool(定长的线程池):核心线程数等于最大线程数,也就是说没有非核心线程

JUC.谈谈线程池_创建线程_03


2.SingleThreadPool(单一的线程池):核心线程数和最大线程数都为1,也就是说只有一条线程来执行任务。适用于顺序执行的任务。

JUC.谈谈线程池_线程池_04


JUC.谈谈线程池_任务队列_05


3.CachedThreadPool(缓存的线程池):没有核心线程,需要注意的是它允许创建的最大线程数为Integer.MAX_VALUE。也就是说无限大。当任务来时就创建线程,没有任务就回收。使用于耗时短任务多的场景。

JUC.谈谈线程池_任务队列_06


4.ScheduledThreadPool(周期性的线程池):按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务

JUC.谈谈线程池_任务队列_07


JUC.谈谈线程池_任务队列_08

那么我们在真实的生产中会用上面的线程池吗?答案是不会!

JUC.谈谈线程池_任务队列_09


阿里巴巴手册中有提到。你看源码的话也能知道!四种线程池的源码在上边自己看!

ThreadPoolExecutor

看过源码可以发现SingleThreadPool 、FixedThreadPool、CachedThreadPool底层其实都是ThreadPoolExecutor!

线程池底层工作原理

先了解一下线程池的7大参数:
1.corePoolSize:核心线程数
2.maximumPoolSize:线程池允许创建的最大线程数
3.keepAliveTime:非核心线程空闲后的最大存活时间
4.TimeUnit: keepAliveTime的时间单位
5.workQueue:任务队列
6.threadFactory:线程工厂,用来创建线程
7.handle:拒绝策略

底层原理:
execute方法

构造ThreadPoolExecutor对象,然后每来一个任务,就调用ThreadPoolExecutor对象的execute方法

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //workerCountOf(c)会获取当前正在运行的worker数量
//如果工作线程数量小于corePoolSize,就创建一个worker然后直接执行该任务
if (addWorker(command, true))
return;
c = ctl.get();
}
//isRunning(c)是判断线程池是否在运行中,如果线程池被关闭了就不会再接受任务
//后面将任务加入到队列中
if (isRunning(c) && workQueue.offer(command)) {
//如果添加到队列成功了,会再检查一次线程池的状态
int recheck = ctl.get();
//如果线程池关闭了,就将刚才添加的任务从队列中移除
if (! isRunning(recheck) && remove(command)
reject(command); //执行拒绝策略
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果加入队列失败,就尝试直接创建worker来执行任务
else if (!addWorker(command, false))
//如果创建worker失败,就执行拒绝策略
reject(command);
}

线程池工作原理:
1.在创建好线程池后开始等待请求
2.当调用excute()方法添加一个任务请求时,线程池会做出如下判断:
2.1当前运行线程数<corePoolSize的时,马上执行该任务
2.2当前运行线程数>=corePoolSize的时,就会把该任务放到任务队列中
2.3当任务队列也满了,且当前运行线程数<maximumPoolSize时,就会创建非核心线程来执行该任务
2.4当前运行线程数>maximumPoolSize且任务队列也满了时,就会启动拒绝策略来拒绝任务。
3.线程池中的线程执行完一个任务后就会从任务队列中取下一个任务执行。
4.当一个非核心线程无事可做的时间超过了keepAliveTime时,他就会被回收。所以,线程池中线程的数量会收缩到corePoolSize!

拒绝策略

Jdk提供了四种内置的拒绝策略:
AbortPolicy(默认):直接抛出RejectedException异常阻止系统正常运行
CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是会调用当前线程池的所在的线程去执行被拒绝的任务
DiscardPolicy:不处理,直接丢弃任务
DiscardOldestPolicy:丢弃任务队列中等待最久的一个任务,然后将任务添加到任务队列中,尝试执行当前任务。

以上内置策略均实现了RejectExecutionHandler接口!也可以通过实现该接口来自定义拒绝策略,如记录日志等。

向线程池提交任务

可以使用两个方法向线程池提交任务:
execute():可以用于提交不需要返回值的任务
sumbit():用于提交需要返回值的任务,线程池会返回一个future对象。

关闭线程池

shutdown():只是将线程状态改为了shutdown,然后中断所有没有在执行任务的线程
shutdownNow():先将线程状态改为了shutdown,然后尝试停止所有正在执行或暂停任务的线程
​ 原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止

当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true

合理配置线程池

可以以下几个角度分析:
PS:N表示CPU核心数,可以通过Runtime.getRuntime().availableProcessors() 获取核心数
1.任务的性质:
CPU密集型:应配置尽可能小的线程,如N+1个的线程池
IO密集型:IO密集型不会一直在执行任务,应配置尽可能多的线程,如N*2个线程。
混合型:
2.任务的优先级:
优先级不同的任务可以使用优先级队列(PriorityQueue)来处理,他可以让优先级高的任务先执行。
3.任务执行的时间:
让执行时间短的任务先执行
4.任务的依赖性:
依赖数据库的连接池任务,因为线程提交SQL后需要等待数据库的返回结果,等待的时间越长,CPU的空闲时间就越长,那么线程数应设置的大一些,这样才能更好地利用CPU。
5.建议使用有界队列
有节队列能增加系统的稳定性和预警能力,可以根据需要设置的大一点。如果使用无界队列就有可能出现线程池中的队列积压撑满内存的现象导致整个系统不可用而不是后台出问题!!!

自己简单的配置一下线程池:

ExecutorService threadPoolExecutor = new ThreadPoolExecutor(5,//核心线程数
10,//线程池的最大线程数
2L, TimeUnit.SECONDS,//线程没有任务执行时最多保持多久时间会终止()
new LinkedBlockingQueue<Runnable>(3),//任务队列
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.DiscardOldestPolicy()//拒绝策略
);
try{
for(int i=0;i<20;i++){
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"正在执行任务!");
try{TimeUnit.SECONDS.sleep(2); }
catch(Exception e){ System.out.println(e.getMessage()); }
});
}
}
catch(Exception e){
System.out.println(e.getMessage());
} finally {
threadPoolExecutor.shutdown();
}

线程池的监控

taskCount线程池需要执行的任务数量,是个近似值。
completedTaskCount线程池在运行过程中已完成的任务数量,小于或等于taskCount,是个近似值。
largestPoolSize线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
poolSize:线程池当前的线程数总量,包括活动的线程与闲置的线程

activeCount:活动的线程数


举报

相关推荐

0 条评论