线程池的简单的底层解析和使用

是什么?

可以想象成一个池子,里面装的是创建好的线程。

有什么用?

我们执行多线程任务的时候,任意创建和销毁线程,会消耗大量的系统资源,线程之间的切换,线程的数量,加锁和释放锁操作,都会造成资源的严重消耗,甚至导致系统程序崩溃,所以为了管理这种杂乱无章的场面,我们就需要有一个工具来管理线程执行任务,于是就有了线程池。

优缺点:
优点:

(1)减低了频繁开启关闭线程的资源消耗。
(2)通过复用以及创建好的空闲状态线程,提高了线程的使用率,提高了系统响应速度
(3)通过控制线程开启的数量,保证内存占用率的健康状态,减少了CPU切换和恢复线程执行任务的成本。
(4)可通过缓冲队列等实现更灵活的线程实现,自定义线程池等,防止出现OOM(Out Of Memory)

缺点:

(1)死锁:涉及到多线程都会有的死锁的可能(A有1,要2;B有2,要3;C有3,要1,僵持不下)。例如:池中的大部分线程都在等待一个等待队列里面的执行任务,但是等待队列里的那个任务又因为没有可执行任务的空闲线程而一直等待,所以就会造成线程池独有的死锁。
(2)内存资源不足/性能下降:每条线程执行任务都会需要一定的内存空间,需要堆栈内存,如果不能合理调整控制线程池的大小,容易造成系统资源的消耗,线程之间切换的开销和加锁释放锁的开销,也会严重影响程序的性能。
(3)线程泄漏:有任务进来的时候,会分配一条线程去执行,但是当执行任务时候发生一些未捕获的Exception,导致线程无法正确执行完目标任务进而无法返还线程到线程池中,甚至有的线程任务是定时执行或者触发执行或者等待用户输入之类的,一直没有达到执行任务的条件,于是一直在等待,这样多次反复,就造成线程池中没有闲置的线程,但是又没办法执行任务,一直卡着。
(4)请求过载:没有做好压力测试,不知道服务器端能扛住多大的请求压力,开启多线程跑任务的时候任务量太大,大量请求冲溃了服务器。

如何工作?

创建出与核心线程数一致的线程数量—》
执行与核心线程数对应的任务数量—》
如果还有未执行的任务并且当前等待队列未满—》
加入等待队列—》
如果等待队列满,且还有任务提交进来—》
判断当前线程数是否等于池内最大线程数—》
不等于且小于的话,开启临时线程执行任务 —》
若最大线程数等于池内线程数且等待队列已满,且还有任务继续提交进来 —》
执行执行的拒绝策略reject handle—》
核心线程执行完任务后调用take()方法继续接任务执行或者阻塞等待keep alive,存活在线程池中等待下一次任务,达到线程复用—》
非核心线程执行完任务后假如没有任务执行了,且超出了预定的过期时间后,执行poll()方法销毁

看看底层:
ThreadPoolExecutor threadPoolExecutor =
        new ThreadPoolExecutor(10,20,10, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
基本参数解释:

线程池的简单的底层解析和使用_第1张图片

线程工厂用来干嘛?

就是创建线程的(核心线程和临时线程都由工厂创建),工厂创建线程的方法是:Thread newThread(), new Thread()方法的底层是实现了Runnable接口的创建方式。(为什么不用继承Thread类?因为Thread类底层也是用实现了Runnable接口哦,而且接口易做拓展,不需要单继承的局限性)
线程池的简单的底层解析和使用_第2张图片
线程池的简单的底层解析和使用_第3张图片
线程池的简单的底层解析和使用_第4张图片
线程池的简单的底层解析和使用_第5张图片

饱和拒绝策略有四种:

(1)AbortPolicy(默认)直接拒绝
(2)CallerRunsPolicy:使用调用者的线程执行任务,不抛出异常不抛弃任务,将任务返还给调用主线程去执行,趁这段时间处理线程池正在执行的任务
(3)DiscardOldestPolicy:放弃队列中等待最久的任务(注意不能跟优先级队列组合使用,不然会把优先级最高的最老的任务抛弃)
(4)DiscardPolicy:抛弃当前任务

阻塞队列拿来干什么?有哪几种?

线程的工作队列用来存放等待执行的线程,有以下几种常用的队列(都是线程安全):
ArrayBlockingQueue:数组阻塞队列,先进先出原则
LinkedBlockingQueue:链表阻塞队列,先进先出,吞吐量高于数组阻塞队列
SynchronousQueue:不存储元素的无界阻塞队列,任务插入操作等待,有空闲的线程调用或者创建新线程时候移除操作,否则插入操作一直阻塞,吞吐量高于链表阻塞队列,不属于一种容器队列,只是类似一种任务分发
PriorityBlockingQueue:优先无界阻塞队列,按照优先级对元素进行排序
(无界就是maximumPoolSizes设置为无界)
线程池的简单的底层解析和使用_第6张图片

有哪几种常见线程池?怎么用?分别对应什么场景?

有四种常见的线程池:
线程池的简单的底层解析和使用_第7张图片
线程池的简单的底层解析和使用_第8张图片
线程池的简单的底层解析和使用_第9张图片
线程池的简单的底层解析和使用_第10张图片

如何关闭一个线程池?

(1).ShutDown:关闭当前线程池,执行该命令后线程池不会马上关闭。而是会暂停接受新的任务,并且等待之前提交的任务都完成之后关闭。所以用该方法关闭线程池,必须保证不会有死锁和永久阻塞的任务发生,不然线程池会无法关闭。
(2).ShutDownNow:马上关闭,但真实情况不是马上关闭,而是暂停接受新的任务,同时把正在执行的任务通过Thread.interrupt()方法打断中止任务的执行,调用.ShutDownNow方法关闭线程池,需要捕获任务执行的Exception。

如何计算服务器最佳线程数?

最佳线程数 = CPU数 * CPU 利用率 * (1+ 任务IO等待毫秒时间 / 任务计算毫秒时间 )
例如:
(1)IO密集型:4核CPU,利用率100% ,计算任务时间:100ms,IO任务时间:400ms
最佳线程数 = 41(1+400/100) = 20
(2)计算密集型:4核CPU,利用率100% ,计算任务时间:800ms,IO任务时间:200ms
最佳线程数 = 41(1+200/800) = 5
总结:IO密集型最佳线程数会远高于计算密集型最佳线程数,并且计算密集型的最佳线程数接近 CPU核心数

为何要计算最佳线程数?一个是优化效率把性价比调到最高,还有就是防止OOM,小弟在另外一篇博客中有总结:

从Executors和ThreadPoolExecutor的区别分析到线程池的OOM

以上就是我的个人总结,如果有什么写的不对的或者总结错了的欢迎指正一下哈

你可能感兴趣的:(Java实战项目分享,Java基础系列)