up:: 线程池构造函数
说明:
(1) 本篇博客的主要内容:
● 是演示JDK给我们提供的4种自动创建线程的方式;可以得出【不推荐采用自动创建线程的方式,去创建线程】的结论;
● 自己根据具体需求,创建线程池时,线程数量的确定原则;
一:线程池应该手动创建还是自动创建?:应该手动创建;
手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险;
自动创建线程,会带来一些问题;
二:我们可以直接调用JDK封装好的构造方法,去自动创建线程池;但这可能会带来一些问题;
1.自动创建线程池的策略一:Executors.newFixedThreadPool();
说明:
(1) 程序内容说明;(这部分不是重点啦)
(2) newFixedThreadPool(int nThreads)原理分析;
● 这种线程池的特点:核心线程数和最大线程数相同,都设置为我们传递参数大小;队列使用的是无界队列;
● 那么很显然,对于这种线程池;因为队列是无界队列,所以,如果任务很多处理不完的时候,任务就可能会堆积在队列中,从而产生OOM错误;
● 这种线程池为什么要使用无界队列?:因为,无界队列可以满足这种线程池功能;因为,这种线程池,最大线程数和核心线程数相同,那么当任务很多的时候,我们不得不使用无界队列去存储新来的任务;
(3) 演示OOM内存溢出异常;
● 为了能够更好的让任务到队列中去排队,以更快的能看到内存溢出,我们把线程池的线程数设为了1;
● 每个任务,我们都让其休眠很长时间,以模拟每个任务的执行都要占用很长时间;从而,能够更快的看到内存溢出;
● 我们执行的任务次数很大;这样能更好的消耗内存,从而能更快的看到OOM异常;
● 自己的PC机器的异常还是挺大的,所以为了能更快的消耗内存;我们这儿设置一下程序的可用异常;
● 运行程序,会报OOM异常;
2.自动创建线程池的策略二:Executors.newSingleThreadExecutor();
说明:
(1) newSingleThreadExecutor()内容说明;
● 这种线程池的特点:核心线程数和最大线程数相同,都设置为了1;队列使用的是无界队列;
● 那么很显然,对于这种线程池;因为队列是无界队列,所以,如果任务很多处理不完的时候,任务就可能会堆积在队列中,占用大量的内存,就可能会产生OOM错误;
●
这种线程池为什么要使用无界队列?:因为,无界队列可以满足这种线程池功能;因为,这种线程池,最大线程数和核心线程数都是1,那么当任务很多的时候,我们不得不使用无界队列去存储新来的任务;
● newSingleThreadExecutor();这种线程池在实际中,使用的不多;
(2) 运行结果;
3.自动创建线程池的策略三:Executors.newCachedThreadPool();
这种方式创建的线程池可以缓存;这种线程池,会自动回收多余的线程;
说明:
(1) newCachedThreadPool()内容说明;
● 这种方式创建的线程池可以缓存;这种线程池,会自动回收多余的线程;
●
那么,因为newCachedThreadPool()创建线程时,没有限制最大线程数;那么,如果任务很多的话,其就会创建很多的线程,这就可能会导致OOM异常;
● 又因为,其核心线程数为0;所以如果长期没有任务的话,其所有的线程都会被销毁;
● 因为,这种线程池,我们没有限制最大线程数,所以,我们可以不需要队列来存储任务;;;;所以,这种线程池的队列,可以使用【new
SynchronousQueue<Runnable()】这种没有容量的直接交接队列;
(2) 运行结果;
4.自动创建线程池的策略四:Executors.newScheduledThreadPool();
这种方式创建的线程池支持定时及周期性的去执行任务;在实际开发中,遇到定时任务的时候,就可以使用这种线程池;
说明:
(1) newScheduledThreadPool()内容说明;
● 这种方式创建的线程池支持定时及周期性的去执行任务;在实际开发中,遇到定时任务的时候,就可以使用这种线程池;
● 这种线程池,核心线程数设为我们传递值,最大线程数没有限制;
● 这种线程池,使用的是DelayedWorkQueue延迟队列;延迟队列的能力,就是可以根据我们的设置的时间先后,把队列中的任务作延迟;这是符合newScheduledThreadPool()这种线程池的的需求的;
(2) 运行结果;
5.四种线程池的总结;
三:所以,比较合适的做法是:根据自己的业务场景去设置线程池参数,手动创建线程池;
1. 根据具体的业务场景,去设置线程池的参数;
● 比如,自身硬件设备的内存有多大;想给线程取什么名字(如果我们在创建线程时,想给线程取一个自己的名字;那么就需要利用自己的线程工厂);
● 还比如,任务被拒绝时,该如何记录日志;
● 再比如,根据我们业务的并发量,来决定线程池的线程数量;
2.线程池的线程数量,设为多少?
(1) 如果任务是CPU密集型(大量的计算的任务;比如加密,计算hash等;执行这些任务时,CPU会是满负荷的;)的,那么线程池的线程数量应该设为CPU核心数的1-2倍;(比如,我们的CPU是8核,那么我们线程池的核心线程数就可以设为8-16之间)
(2) 如果任务是耗时IO型(读写数据库、文件,网络通信等;一旦涉及到网络通信时,一般CPU是不工作的;或者读写文件时,外设的速度是低于CPU的速度的,所以CPU很可能会空闲)的,那么线程池的线程数量可以设为CPU核心数的很多倍;(比如,我们的CPU是8核,那么我们线程池的核心线程数就可以设为80;;;;虽然有80个线程,但是实际执行的时候,因为读写速度或者网络速度较慢,所以会有很多线程在等待,即会有很多线程是没有占用CPU的,,但是,由于我们设置了80个线程,这就就能充分利用CPU)
(3) 一个可以参考的线程数计算方法是:线程数=CPU核心数*(1 + 平均等待时间/平均工作时间);
● 平均等待时间长,表示线程的等待时间;;平均工作时间,指的是线程占用数据库的时间;
● 比如,对于读取数据库这种业务,线程大部分时间是等待的,其等待时间假如说是100秒,而其占用CPU的计算时间假如说是1秒;那么,其线程数=CPU核心数*(1+100/1);假如CPU是8核,那么此时线程池的核心线程数就可以设为808;
(4) 其实,整体的原则就是:
● 如果一个业务在执行的时候,大部分时间是需要占用CPU的,那么线程数就需要设置的小一点;(因为,业务的大部分时间已经占用了CPU了,CPU的空闲时间已经不多了,即CPU的可压榨空间已经很少了;继续创建线程的意义不大);
● 如果一个业务在执行的时候,大部分时间是不需要占用CPU的,那么线程数就可以设置大一点;即,我们尽量多开几个线程,以充分压榨CPU;
(5) 在实际开发中,最精准的策略是:根据不同的程序去做压测;根据压测结果,得到一个相对合适的线程数量;