4 线程池应该手动创建还是自动创建
手动创建更好,因为这样可以让我们更加了解线程池的运行规则,避免资源耗尽的风险。
4.1 直接调用JDK封装好的线程池会带来的问题
newFixedThreadPool
<code>public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()); }/<runnable>/<code>
newFixedThreadPool线程池通过传入相同的corePoolSize和maxPoolSize可以保证线程数量固定,0L的keepAliveTime表示时刻被销毁,workQueue使用的是无界队列。
这样潜在的问题就是当处理任务的速度赶不上任务提交的速度的时候,就可能会让大量任务堆积在workQueue中,从而引发OOM异常。
4.2 演示newFixedThreadPool内存溢出问题
<code>/** * 演示newFixedThreadPool线程池OOM问题 */public class FixedThreadPoolOOM { private static ExecutorService executorService = Executors.newFixedThreadPool(1); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { executorService.execute(new SubThread()); } }}class SubThread implements Runnable { @Override public void run() { try { //延长任务时间 Thread.sleep(1000000000); } catch (InterruptedException e) { e.printStackTrace(); } }}/<code>
更改JVM参数
运行结果
4.3 newSingleThreadExecutor
<code>public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 1000; i++) { executorService.execute(new Task()); } }}/<code>
使用线程池打印线程名
查看newSingleThreadExecutor源码
<code>public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()));}/<runnable>/<code>
从源码可以看出newSingleThreadExecutor和newFixedThreadPool基本类似,不同的只是corePoolSize和maxPoolSize的值,所以newSingleThreadExecutor也存在内存溢出问题。
4.4 newCachedThreadPool
newCachedThreadPool也被称为可缓存线程池,它是一个无界线程池,具有自动回收多余线程的功能。
<code>public class CachedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executorService.execute(new Task()); } }}/<code>
<code>public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<runnable>());}/<runnable>/<code>
newCachedThreadPool的maxPoolSize设置的值为Integer.MAX_VALUE,所以可能会导致线程被无限创建,最终导致OOM异常。
4.5 newScheduledThreadPool
该线程池支持周期性任务的执行
<code>public class ScheduledThreadPoolTest { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);// scheduledExecutorService.schedule(new Task(), 5, TimeUnit.SECONDS); scheduledExecutorService.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS); }}/<code>
<code>public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());}/<code>
4.6 正确的创建线程池的方法
根据业务场景不同,自己设置线程池参数,例如内存有多大,自己取线程名字等。
4.7 线程池里的线程数量设置多少比较合适?
CPU密集型(加密、计算hash等):最佳线程数设置为CPU核心数的1——2倍。
耗时I/O型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍,以JVM监控显示繁忙情况为依据,保证线程空闲可以衔接上。参考Brain Goezt推荐的计算方法:线程数=CPU核心数 × (1+平均等待时间/平均工作时间)
5 对比线程池的特点
FixedThreadPool:通过手动传入corePoolSize和maxPoolSize,以固定的线程数来执行任务
SingleThreadExecutor:corePoolSize和maxPoolSize默认都是1,全程只以1条线程执行任务
CachedThreadPool:它没有需要维护的核心线程数,每当需要线程的时候就进行创建,因为它的线程存活时间是60秒,所以它也凭借着这个参数实现了自动回收的功能。
ScheduledThreadPool:这个线程池可以执行定时任务,corePoolSize是通过手动传入的,它的maxPoolSize为Integer.MAX_VALUE,并且具有自动回收线程的功能。
5.1 为什么FixedThreadPool和SingleThreadExecutor的Queue是LinkedBlockingQueue?
因为这两个线程池的核心线程数和最大线程数都是相同的,也就无法预估任务量,所以需要在自身进行改进,就使用了无界队列。
5.2 为什么CachedThreadPool使用的Queue是SynchronousQueue?
因为缓存线程池的最大线程数是“无上限”的,每当任务来的时候直接创建线程进行执行就好了,所以不需要使用队列来存储任务。这样避免了使用队列进行任务的中转,提高了执行效率。
5.3 为什么ScheduledThreadPool使用延迟队列DelayedWorkQueue?
因为ScheduledThreadPool是延迟任务线程池,所以使用延迟队列有利于对执行任务的时间做延迟。
5.4 JDK1.8中加入的workStealingPool
- workStealingPool适用于执行产生子任务的环境,例如进行二叉树的遍历。
- workStealingPool具有窃取能力。
- 使用时最好不要加锁,而且不保证执行顺序。
閱讀更多 青芽草 的文章