疑问
为什么要用线程池,或者线程池为了解决什么问题而存在?
直接创建线程的弊端
- 每次都需要初始化对象,性能极差
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,甚至有可能会因为占用过多资源而导致系统死机或者出现OOM问题
- 缺少更多的功能,比如定期定时执行
介绍
在正式解答上面的疑问之前,我们先了解一下线程池是什么,线程池其实就是在我们使用线程之前提前创建若干个可执行的线程放入到一个容器中,就相当于是一个池子,需要的时候呢,就从这个容器/池中获取线程,不用自己去创建,使用完之后不需要销毁线程而是直接放回池中,以此减少创建和销毁线程对象的开销
解答
我们在面向对象的编程里,创建和销毁对象其实是一件很浪费时间的事,在Java中就更不要说了,JVM想跟踪每一个对象进行管理,为了能够在对象销毁后进行垃圾回收,所以为了更好的提高这方面的性能和效率,最好的一个手段就是尽可能的减少创建和销毁对象的次数,特别是一些很消耗资源的对象,这就是池化资源技术产生的原因,说到池化资源技术,其实说到线程池,大家应该对数据库连接池应该不陌生吧,其实都是为了解决同一个问题
线程池的好处
- 重用存在的线程,减少对象创建、消亡的开销,性能极佳
- 可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争和阻塞
- 提供定时执行、定期执行、单线程、并发数控制等功能
- 提供支持线程池监控的方法,可对线程池的资源进行实时监控
线程池工具介绍
Java有一个Executor接口定义了一个执行线程的工具,它的一个子类,线程池接口类ExecutorService,工具类Executors提供了一些静态工厂方法,生成了一些常用的线程池,为了解决复杂的线程池配置,下面介绍一下工具类Executors提供的方法:
- newSingleThreadExecutor:创建一个单线程的线程池,意思是这个线程池只有一个线程在工作,也就是可以说是单线程串行执行所有的任务,如果这个唯一的线程因为某种原因或者异常结束执行了,那么就会有新的一个进程来补上空缺继续执行,它保证了所有任务的执行顺序按照任务的提交顺序执行
- newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,一直创建到线程池的最大值,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为异常而结束的话,这个线程池就会再补充一个新的线程来继续执行
- newCachedThreadPool:创建一个可缓存的线程池,意思就是说如果线程池的大小超过了处理任务所需要的线程数量的话,那么就会回收部分空闲(是指60秒不执行任务的)线程,当任务数增加时,这个线程池可以自动的添加心线程来处理任务,这个线程对线程池没有大小限制,线程池的大小完全依赖于操作系统。根据操作系统能够创建到最大线程数
- newScheduledThreadPool:创建一个大小无限的线程池,这个线程池支持定时以及周期性执行任务的需求
- newSingleThreadExecutor:创建一个单线程的线程池,这个线程池支持定时以及周期性执行任务的需求
线程池实例的状态
- Running:运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务
- Shutdown: 关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于Running状态时, 调用shutdown()方法会使线程池进入该状态
- Stop: 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于Running或 Shutdown状态,调用shutdownNow()方法,会使线程池进入该状态
- Tidying: 如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量为0),线程池就会进入该状 态。
- Terminated: 处于Tidying状态的线程池调用terminated()方法,会使用线程池进入该状态
这里需要注意的是:不需要对线程池的状态做一些处理,因为线程池的状态是线程池内部根据方法自行定义和处理的
建议
1、CPU密集型任务,需要尽量压榨CPU,可以将大小设置为NCPU+1,也就是CPU数量+1
2、IO密集型任务,可以设置为2*NCPU,也就是CPU数量*2
源码体验
代码可以copy体验一下
class MyTask implements Callable<Integer> { private int upperBounds; public MyTask(int upperBounds) { this.upperBounds = upperBounds; } @Override public Integer call() throws Exception { int sum = 0; for(int i = 1; i <= upperBounds; i++) { sum += i; } return sum; } } ------------------------------------------------------ public static void main(String[] args) throws Exception { List<Future<Integer>> list = new ArrayList<>(); ExecutorService service = Executors.newFixedThreadPool(10); for(int i = 0; i < 10; i++) { list.add(service.submit(new MyTask((int) (Math.random() * 100)))); } int sum = 0; for(Future<Integer> future : list) { sum += future.get(); } System.out.println(sum); }
如果需要更好的线程池性能,个人比较推荐使用newFixedThreadPool方法来创建线程池