如果线程数达到最大,且工作队列也满,此时再进来任务,则直接返回该任务,到调用它的线程中去执行
2. 为啥阿里不建议使用 Executors来创建线程池?原话如下:
我们可以写几个代码来测试一下
先测试FixedThreadPool,代码如下:
public class FixedThreadPoolDemo { public static void main(String[] args) { // 创建一个固定容量为10的线程池,核心线程数和最大线程数都为10 ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 1_000_000; i++) { try{ executorService.execute(()->{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); }catch (Exception e){ e.printStackTrace(); } } } }这里我们需对VM参数做一点修改,让问题比较容易复现
如下所示,我们添加-Xmx8m -Xms8m到VM option中(-Xmx8m:JVM堆的最大内存为8M, -Xms8m,JVM堆的初始化内存为8M):
此时点击运行,就会发现报错如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371) at com.jalon.concurrent.chapter6.FixedThreadPoolDemo.main(FixedThreadPoolDemo.java:21)我们来分析下原因
首先,newFixedThreadPool内部用的工作队列为LinkedBlockingQueue,这是一个无界队列(容量最大为Integer.MAX_VALUE,基本上可一直添加任务)
如果任务插入的速度,超过了任务执行的速度,那么队列肯定会越来越长,最终导致OOM
CachedThreadPool也是类似的原因,只不过它是因为最大线程数为Integer.MAX_VALUE;
所以当任务插入的速度,超过了任务执行的速度,那么线程的数量会越来越多,最终导致OOM
那我们要怎么创建线程池呢?
可以用ThreadPoolExecutor来自定义创建,通过为最大线程数和工作队列都设置一个边界,来限制相关的数量,如下所示:
public class ThreadPoolExecutorDemo { public static void main(String[] args) { ExecutorService service = new ThreadPoolExecutor( 1, // 核心线程数 1, // 最大线程数 60L, // 空闲时间 TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), // 数组工作队列,长度1 new ThreadPoolExecutor.DiscardPolicy()); // 拒绝策略:丢弃 for (int i = 0; i < 1_000_000; i++) { // 通过这里的打印信息,我们可以知道循环了3次 // 原因就是第一次的任务在核心线程中执行,第二次的任务放到了工作队列,第三次的任务被拒绝执行 System.out.println(i); service.execute(()->{ // 这里会报异常,是因为执行了拒绝策略(达到了最大线程数,队列也满了,此时新进来的任务就会执行拒绝策略) // 这里需要注意的是,抛出异常后,代码并不会退出,而是卡在异常这里,包括主线程也会被卡住(这个是默认的拒绝策略) // 我们可以用其他的拒绝策略,比如DiscardPolicy,此时代码就会继续往下执行 System.out.println(Thread.currentThread().getName()); }); } try { Thread.sleep(1000); System.out.println("主线程 sleep "); } catch (InterruptedException e) { e.printStackTrace(); } } } 3. 线程池的生命周期 ExecutorServiceExecutor接口默认只有一个方法void execute(Runnable command);,用来执行任务
任务一旦开启,我们就无法再去插手了,比如停止、监控等