Java提供了4种线程池:
newCachedThreadPool
newFixedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
你可以通过Executors来实例化这四种线程池。
查看源码会发现,这四种线程池都直接或者间接获取的ThreadPoolExecutor实例 ,只是实例化时传递的参数不一样。所以如果java提供的四种线程池满足不了我们的需求,我们可以创建自定义线程池。
ThreadPoolExecutor的构造方法如下:
如果你对这ThreadPoolExecutor不了解,可以参考有界、无界队列对ThreadPoolExcutor执行的影响
接下来我们来看下每种线程池的设计思想
newCachedThreadPool
它的实例化方法如下:
对于 CachedThreadPool 而言,为了避免新提交的任务被拒绝,它选择了无限制的 maximumPoolSize(Integer.MAX_VALUE),所以既然它的线程的最大数量是无限的,也就意味着它的线程数不会受到限制,那么它就不需要一个额外的空间来存储那些 Task,因为每个任务都可以通过新建线程来处理。
弊端:
当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。
对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常
newFixedThreadPool
采用的存储任务的队列是LinkedBlockingQueue ,默认是无界队列。
由于 FixedThreadPool 的线程数是固定的,在任务激增的时候,它无法增加更多的线程来帮忙处理 Task,所以需要像 LinkedBlockingQueue 这样没有容量上限的 Queue 来存储那些还没处理的 Task。
如果所有的 corePoolSize 线程都正在忙,那么新任务将会进入阻塞队列等待,由于队列是没有容量上限的,队列永远不会被填满,这样就保证了对于线程池 FixedThreadPool 和 SingleThreadExecutor 而言,不会拒绝新任务的提交,也不会丢失数据。
弊端:由于使用的是LinkedBlockingQueue无界队列,在资源有限的时候容易引起OOM异常
newSingleThreadExecutor
同newFixedThreadPool一样,只不过corePoolSize和maximumPoolSize都为1,但是由于采用的是无界队列,所以会导致OOM
newScheduledThreadPool
对于 ScheduledThreadPool 而言,它使用的是 DelayedWorkQueue。延迟队列的特点是:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。
我们来举个例子:例如我们往这个队列中,放一个延迟 10 分钟执行的任务,然后再放一个延迟 10 秒钟执行的任务。通常而言,如果不是延迟队列,那么按照先进先出的排列规则,也就是延迟 10 分钟执行的那个任务是第一个放置的,会放在最前面。但是由于我们此时使用的是阻塞队列,阻塞队列在排放各个任务的位置的时候,会根据延迟时间的长短来排放。所以,我们第二个放置的延迟 10 秒钟执行的那个任务,反而会排在延迟 10 分钟的任务的前面,因为它的执行时间更早。
我们选择使用延迟队列的原因是,ScheduledThreadPool 处理的是基于时间而执行的 Task,而延迟队列有能力把 Task 按照执行时间的先后进行排序,这正是我们所需要的功能。
弊端:同newCachedThreadPool一样,由于maximumPoolSize为最大整数,且队列使用的是DelayedWorkQueue无界队列,可呢个会导致OOM
总结:
综合来讲java提供的这4个线程池,由于【maximumPoolSize设置的是最大整数】或者【使用的队列是无界队列】,可能会导致OOM,所以不建议使用。应该根据自己的需要来通过ThreadPoolExecutor来构造自己的线程池