线程池的优点
- 减少资源的创建 => 减少了线程的创建
- 降低系统的开销 => 省掉了创建线程的时间
- 提高了稳定性 => 避免了创建太多的线程而导致OOM
Executors创建线程池的方式
- 创建返回ThreadPoolExecutor对象
- 创建返回ScheduleThreadPoolExecutor
- 创建返回ForkJoinPool对象
Executor创建ThreadPoolExecutor对象
newCachedThreadPool方法 => 创建可缓存的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
队列永远是满的,而SynchronousQueue队列是不存储元素的队列,只进行转发和传送,所以最终都会创建非核心线程来执行任务。因为最大线程数设置的非常大可以认为可以无限创建线程,在资源有限的情况下会引起OOM
newSingleThreadExecutor方法 => 创建单线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
当有任务提交时,能保障执行顺序就是提交顺序,会创建个核心线程来执行任务,当超过核心线程数量时,将会放入队列中,当核心线程执行任务异常时会调用那个非核心线程来执行。又因为LinkedBlockingQueue是长度为Max_value的队列,可以认为是无界队列,当往队列中插入无限多的任务时,在资源有限的情况下会发生OOM。并且因为无界队列可以认为与最大线程数相关的参数都无效maximumPoolSize和keepAliveTime(默认情况下只针对非核心线程)都会无效。
newFixedThreadPool方法 => 创建固定长度的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
当请求的任务数到达核心线程数的大小时,也不会再开新线程了因为最大线程数=核心线程数,与单线程线程池一样会产生无界队列的问题,唯一的区别就是核心线程数不同。
因为这三种创建ThreadPoolExecutor的方式都会造成OOM所以不建议使用Executor创建线程池,推荐自己去创建ThreadPoolExecutor,new 一个ThreadPoolExecutor(...)
正确的配置线程池 => 为了使程序的运行速度最大化
通过 相应的场景 + 正确的线程个数 => 运行速度 ,也就是充分利用CPU和I/O利用率
场景
- CPU密集型程序
单核CPU下创建4个线程来处理1+2+....100亿
多核的情况下
单核CPU处理CPU密集型程序,这种情况不适合使用多线程。 多核CPU处理CPU密集型程序,此时可以最大化的利用CPU核心数,应用并发编程来提高效率。
- I/O密集型程序
线程等待时间的占比越高,需要越多线程;线程CPU时间占比越高,需要越少线程。
创建多少个线程合适
- CPU密集型程序创建线程数
- 理论上
线程数量 = CPU 核数(逻辑),但是实际上线程数量 = CPU核数 + 1多出来的一个线程是用来做兜底的,当出现问题时就会用到。
- 理论上
- I/O密集型程序创建线程数
- 单个CPU核心的最佳线程数 = (1/CPU利用率)= 1 + (I/O耗时/CPU耗时)
- 多个CPU核心的最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU * 1 + I/O耗时/CPU耗时
- 假如全是I/O操作,那就设置为2N + 1(兜底的),N为CPU核数