首先看一下类图
线程池的执行流程
线程池整个流程:
- 创建线程池,开始等待请求
- 调用execute()方法添加一个请求任务时,线程池会判断
- 如果正在运行的线程数雄安与核心线程数,那么会创建线程运行这个任务
- 如果正在运行的线程数大于或者等于核心线程数,则将这个任务放入队列中
- 如果这个队列满了,而且线程数论小于最大线程数,则创建非核心线程执行任务
- 如果队列满了,而且正在运行的线程数也大于或者等于最大线程数,则线程池启动包和拒绝策略来处理
- 当一个线程完成任务时,则从队列里取下一个任务来执行。
- 当一个线程无事可走超过了活跃时间,则判断当前线程数是否大于核心线程,如果大于则关闭线程到核心线程数,否则核心数的线程个数保持活跃
线程池的拒绝策略
JDK中默认提供了4种拒绝策略,并且都 实现RejectedExecutionHandle接口
- AbortPolicy(默认):直接抛出RejectExecutionException异常阻止系统正常运行
- CallerRunsPolicy:调用者运行“一种调节机制,该策略即不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新的任务流量”
- DiscardOldestPolic:抛弃队列中等待的最久的任务,然后把当前任务加入队列中,再尝试再次提交当前任务。
- DiscardPolic:该策略默默地丢弃无法处理的任务,不予以任何处理也不抛弃异常,如果允许任务丢失,这是一种最后的策略。
先看一下线程池的基本使用:
/**
*@Author:苏牧夕
*@Date:2020/3/19 23:24
*@Version 1.0
*/
public class ThreadPool {
//单例线程池
static class InstancePool{
public static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2,//核心数
4,
// Runtime.getRuntime().availableProcessors(),//获取cpu的核数
30, //空闲非线程的存活时间
TimeUnit.SECONDS,//时间类型
new LinkedBlockingQueue<>(2),//阻塞队列
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()//包和拒绝策略
);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
AtomicInteger ctl = new AtomicInteger();
//根据构造线程池的参数可以指定执行的最大任务数是阻塞队列大小+cpu核数
//这里直接使用2+4=6任务数
for (int i=0 ; i<30 ; i++){
int j=i;
InstancePool.poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+",任务编号:"+j);
});
}
InstancePool.poolExecutor.shutdown();
}
//===============运行结果===============
/**
* 使用默认拒绝策略
* 从结果看到,任务执行到第7个就直接拒绝了抛异常
* pool-1-thread-1,任务编号:0
* Exception in thread "main" pool-1-thread-1,任务编号:2
* pool-1-thread-1,任务编号:3
* java.util.concurrent.RejectedExecutionException: Task jdk8.ThreadPool$$Lambda$1/1149319664@568db2f2 rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]
* at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
* at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
* at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
* at jdk8.ThreadPool.main(ThreadPool.java:33)
* pool-1-thread-2,任务编号:1
* pool-1-thread-3,任务编号:4
* pool-1-thread-4,任务编号:5
*/
/**
* 使用DiscardOldestPolicy() 丢弃老任务执行新任务,但不抛出异常
* pool-1-thread-2,任务编号:1
* pool-1-thread-2,任务编号:28
* pool-1-thread-2,任务编号:29
* pool-1-thread-1,任务编号:0
* pool-1-thread-4,任务编号:5
* pool-1-thread-3,任务编号:4
*/
/**
* DiscardPolicy(),不抛异常,也不处理
*pool-1-thread-2,任务编号:1
* pool-1-thread-4,任务编号:5
* pool-1-thread-1,任务编号:0
* pool-1-thread-4,任务编号:3
* pool-1-thread-2,任务编号:2
* pool-1-thread-3,任务编号:4
*/
/**
* CallerRunsPolicy() 不抛出异常,交给调用者线程去处理
* 可以发现任务全部执行完成
* ....
*main,任务编号:28
* main,任务编号:29
* pool-1-thread-2,任务编号:1
* pool-1-thread-2,任务编号:2
* pool-1-thread-2,任务编号:3
* pool-1-thread-1,任务编号:0
* pool-1-thread-3,任务编号:4
* pool-1-thread-4,任务编号:5
*/
}
从上面各种拒绝策略及运行结果可以看出,线程执行完成自己任务后就从任务队列里面取任务来执行,但是会因为线程池装载任务的数量限制而有可能出现任务丢失,这也是为什么后来出现了支持sumit提交一个callable的任务,该方法是支持异步任务,任务提交给线程后会立刻继续接其他任务,就好像工厂接单生产任务,先接单然后慢慢生产,Callable底层是通过FutureTask包装起来的,因此个人感觉submit+callable来提交任务比较好,丢失任务的几率更小。
下面看一下基于默认拒绝策略下使用callable+sumit来提交任务的结果:
public class ThreadPool {
//单例线程池
static class InstancePool{
public static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2,//核心数
4,
// Runtime.getRuntime().availableProcessors(),//获取cpu的核数
30, //空闲非线程的存活时间
TimeUnit.SECONDS,//时间类型
new LinkedBlockingQueue<>(2),//阻塞队列
new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
// new ThreadPoolExecutor.CallerRunsPolicy()//包和拒绝策略
);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
AtomicInteger ctl = new AtomicInteger();
//根据构造线程池的参数可以指定执行的最大任务数是阻塞队列大小+cpu核数
//这里直接使用2+4=6任务数
for (int i=0 ; i<30 ; i++){
int j=i;
InstancePool.poolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + ",任务编号:" + j);
// return j;
}).get();
}
InstancePool.poolExecutor.shutdown();
}
/**
* pool-1-thread-1,任务编号:0
* pool-1-thread-2,任务编号:1
* pool-1-thread-1,任务编号:2
* pool-1-thread-2,任务编号:3
* pool-1-thread-1,任务编号:4
* pool-1-thread-2,任务编号:5
* pool-1-thread-1,任务编号:6
*..................
* pool-1-thread-1,任务编号:20
* pool-1-thread-2,任务编号:21
* pool-1-thread-1,任务编号:22
* pool-1-thread-2,任务编号:23
* pool-1-thread-1,任务编号:24
* pool-1-thread-2,任务编号:25
* pool-1-thread-1,任务编号:26
* pool-1-thread-2,任务编号:27
* pool-1-thread-1,任务编号:28
* pool-1-thread-2,任务编号:29
*/
可以看到执行完成了所以任务,线程复用率更大了
下面是线程池源码分析:
首先分析ThreadPoolExecutor这个核心创建线程池类:
-
先分析一下全局变量的作用
//用来标记线程池的状态(高三位),和线程数(低29位) private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //线程个数掩码位数 private static final int COUNT_BITS = Integer.SIZE - 3; //线程最大个数(低29位)00011111111111111111111111111111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 下面几种是线程池状态, //(高3位):11100000000000000000000000000000 //该状态标识接收任务并且处理阻塞队列的任务 private static final int RUNNING = -1 << COUNT_BITS; //(高3位):00000000000000000000000000000000 //拒绝任务但是处理阻塞队列里面的任务 private static final int SHUTDOWN = 0 << COUNT_BITS; //(高3位):00100000000000000000000000000000 //拒绝新任务并且抛弃阻塞队里的任务,同时会中断正在处理的任务 private static final int STOP = 1 << COUNT_BITS; //(高3位):01000000000000000000000000000000 //所以任务的执行完成,当前线程池活动线程为0,准备调用terminated方法 private static final int TIDYING = 2 << COUNT_BITS; //(高3位):01100000000000000000000000000000 //终止状态 private static final int TERMINATED = 3 << COUNT_BITS; // 获取高三位 运行状态 private static int runStateOf(int c) { return c & ~CAPACITY; } //获取低29位 线程个数 private static int workerCountOf(int c) { return c & CAPACITY; } //计算ctl新值,线程状态 与 线程个数 private static int ctlOf(int rs, int wc) { return rs | wc; } //任务阻塞队列 private final BlockingQueue<Runnable> workQueue; //重入锁,在添加工作线程,修改状态等地方用到 private final ReentrantLock mainLock = new ReentrantLock(); //存储工作线程的哈希表 private final HashSet<Worker> workers = new HashSet<Worker>(); //锁的条件,用来配合重入锁实现唤醒等待相关操作 private final Condition termination = mainLock.newCondition(); //用来追踪线程池实际大小。 private int largestPoolSize; //完成的任务数 private long completedTaskCount; //线程工厂 private volatile ThreadFactory threadFactory; //拒绝策略 private volatile RejectedExecutionHandler handler; //非核心线程空闲存活时间 private volatile long keepAliveTime; //核心线程是否一直存活还是存活一段时间 private volatile boolean allowCoreThreadTimeOut; //核心线程的大小 private volatile int corePoolSize; //最大线程数 private volatile int maximumPoolSize; //使用默认拒绝策略,直接抛出异常 private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); //下面是相关权限参数 private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread"); private final AccessControlContext acc;
-
核心构造方法
//这是完整参数的构造方法,其他那些不完整的使用默认值 public ThreadPoolExecutor( int corePoolSize,// 核心线程数 int maximumPoolSize, //最大线程数 long keepAliveTime,//非核心线程空闲时数存活时间 TimeUnit unit,//时间类型 BlockingQueue<Runnable> workQueue,//任务的阻塞队列 ThreadFactory threadFactory,//线程工厂 RejectedExecutionHandler handler//饱和拒绝策略 ) { //判断参数合法性 if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); //几大参数进行赋值 this.acc = System.getSecurityManager() == null ?null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
-
下面中execute()方法开始分析
execute方法中执行逻辑就是开头写的几种情况
//该方法是向线程池提交runnable任务的方法 public void execute(Runnable command) { //检测参数是否非法 if (command == null) throw new NullPointerException(); //下面开始便上开头说的几种情况 //获取当前存活的线程数 int c = ctl.get(); //1.如果小于核心线程数则直接调用addWorker创建工作线程, //并且开始任务,该方法后面分析 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; // 如果创建失败,则重写获取存活线程数 c = ctl.get(); } //2.核心数满的情况或者其他情况使工作线程创建失败时, //判断是否是接收任务并处理的状态,如果是则直接向任务队列添加任务 if (isRunning(c) && workQueue.offer(command)) { //再次检查存活的工作线程数 int recheck = ctl.get(); //4.再次判断如果非接收任务和处理的状态则异常该任务,执行拒绝策略。 if (! isRunning(recheck) && remove(command)) reject(command); //如果工作线程数等于0即线程池为空还没有形成,则直接添加工作线程 else if (workerCountOf(recheck) == 0) addWorker(null, false); } //3.队列满了,则添加新工作形成,如果新增失败则执行拒绝策略 else if (!addWorker(command, false)) reject(command); }
-
接着分析线程池是如何添加新的工作线程的,即addWorker方法
添加工作线程的基本逻辑:
-
先获取线程池和存储线程个数的集合体,然后根据集合体获取当前线程池的状态,然后对线程池的状态和工作队列进行判断,如果线程池已经处于不接收任务或者拒绝处理的状态并且工作队列不为空则直接返回false表示添加工作线程失败。
-
如果条件1成立,则再通过循环cas对工作线程个数进行修改+1,不过会重新获取当前线程池工作线程个数看是否超限制,如果超限制也直接返回失败,否则进行cas修改工作线程个数,如果修改成功直接跳出大循环进入后续相关操作,否则失败则判断当前线程池状态是否与刚刚进入大循环的状态一致如果不一致,则重新进入大循环,如果状态一致则继续循环cas修改操作,直到修改一次成功后退出循环。
-
如果条件2通过cas修改工作线程个数成功,则进行最后两个步骤:
-
把新增的工作线程添加的线程池的工作线程集合中;
首先根据传入的任务创建一个工作线程对象实例,并且获取该对象实例的线程,判断是否为null,如果非null,则开始用重入锁上锁保证原子性下在进行添加操作,添加操作过程是:先获取当前线程池的状态和判断是否还可以接收任务和处理任务,如果还可以接收并处理任务,在进一步判断该线程是否存活,如果存活则添加到工作线程集合中,修改添加标记,然后获取工作线程集合的大小与追踪大小比较并且进行相关修改,然后解锁。
-
在线程添加到工作线程集合成功的前提下,让该线程开始执行任务。
线程开始的过程:首先更加上述添加标记是否成功添加,如果成功添加,则让线程开始工作,并修改线程是否开始运行的标记。
-
-
如果条件2通过,最后对上述添加标记进行判断,如果没添加成功,则调用添加失败的处理方法,先上锁保证原子性,再判断该工作线程对象实例是否为null,如果不为空则,在工作线程的集合里面移除该对象。
private boolean addWorker(Runnable firstTask, boolean core) { //这是一个标记,后面使用这个进行再次进入循环用 retry: for (;;) { //获取当前线程池的状态+线程个数变量的组合值 int c = ctl.get(); //获取线程池当前状态 int rs = runStateOf(c); //检查队列是否为空,和当前线程池是否处于接收任务和处理任务状态,如果不则直接返回false表示失败 // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; //通过循环进行cas修改工作线程存活的个数 for (;;) { //根据c组合值来获取工作线程数 int wc = workerCountOf(c); //判断工作线程数是否大于容量或者是否大于最大核心数,如果超了则直接返回false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //如果上述条件都通过,则通过cas修改工作线程个数+1 if (compareAndIncrementWorkerCount(c)) break retry;//如果成功修改则直接退出retr这个大循环,以上是只有修改一次成功则循环结束。 //再次获取 线程池状态和各种线程个数的组合 c = ctl.get(); // Re-read ctl //重写判断状态,如果和开始进来的状态不同则重新开始retry大循环。 if (runStateOf(c) != rs) continue retry; // 否则继续内循环cas,直到修改成功 } } //到这里证明已经通过cas成功修改了工作线程数的个数 //定于两个布尔类型,来判断是否开始工作,是否添加的工作线程队列里面 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //根据任务创建一个工作线程对象 w = new Worker(firstTask); //获取该工作线程对象的线程 final Thread t = w.thread; if (t != null) { //先通过重入锁开始上锁再进行相关操作,防止中间有其他操作进行 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //获取线程池当前状态 int rs = runStateOf(ctl.get()); //判断线程池是否处于接收任务和处理任务状态 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //进一步判断,该工作线程是否是存活的 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //如果存活则把该线程添加到工作线程的集合里面 workers.add(w); //获取工作线程集合的大小 int s = workers.size(); //如果当下工作线程数大于追踪的线程数,则修改 if (s > largestPoolSize) largestPoolSize = s; //修改状态改为添加成功 workerAdded = true; } } finally { //进行完添加后,直接解锁 mainLock.unlock(); } //如果添加成功的话,则线程直接开始任务 if (workerAdded) { t.start(); //修改状态 workerStarted = true; } } } finally { //如果线程开始失败,则调用添加失败的方法进行操作。 if (! workerStarted) addWorkerFailed(w); } return workerStarted; } //添加各种线程失败时调用的方法。 private void addWorkerFailed(Worker w) { //上锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //如果工作线程对象不为null的情况下调用工作线程集合进行移除该对象实例 if (w != null) workers.remove(w); //调用cas修改工作线程数 decrementWorkerCount(); //尝试终止 tryTerminate(); } finally { //最后解锁 mainLock.unlock(); } }
-
-
接着分析工作线程类的run方法
简单来阐述一下工作线程对象worker调用run方法后的执行逻辑以及执行完自己任务后如何从任务队列获取来执行:
- 首先调用run方法后,在通过调用runWorker方法该方法是真正执行任务的方法,进入该方法后,首先获取当前线程用于中断,因为改方法是允许被中断的,再获取worker工作线程对象的任务,然后修改工作线程对象的任务置空,然后进入while循环判断任务是否为空,如果是空则通过getTask方法从任务队列里面获取任务
- 从getTask方法获取任务的逻辑如下
- 判断线程池的状态和任务阻塞队列是否为空,如果为空或者状态以及处于非处理任务和接收任务状态,则通过cas修改存活的工作线程数,直接返回null
- 工作线程数超过了最大线程数或者超时并且再次检查任务队列是否为null,如果条件成立通过cas修改工作线程数-1,直接返回null
- 如果条件1和条件2都成立,则开始从阻塞任务队列里面获取任务来执行,获取有两种方式一种是有时间限制,一种是没有时间限制。
- 如果任务不为空,则加锁,来保证原子性操作,在操作钱,再次判断线程池的状态是否符合执行任务和是否处于中断状态,如果是则进行中断,否则执行一些前置操作(默认是空实现),然后执行任务run,执行后再进行一些后置操作(默认也是空实现),并且对改工作线程的统计完成任务数+1,然后释放锁,一次任务完成后,继续循环从阻塞队列里获取任务,一直到没有任务后结束循环
- 最后通过processWorkerExit方法判断是否要把当前工作线程对象移出工作线程集合(队列),这里移除条件会根据当前任务队列以及线程池的核心线程数来进行相关判断。
//该方法是worker类的run方法,调用了ThreadPoolExecuto方法Workfn public void run() { runWorker(this); } final void runWorker(Worker w) { //获取当前线程 Thread wt = Thread.currentThread(); //获取工作线程对象的任务 Runnable task = w.firstTask; //获取任务后置空 w.firstTask = null; //该工作线程允许允许中断 w.unlock(); // allow interrupts boolean completedAbruptly = true; try {l 当前工作线程对象实例任务不为空,(或者当前工作线程的对象实例任务为空,则通过getTask方法从任务阻塞队列里面获取任务,如果还是获取不到任务则跳出循环) while (task != null || (task = getTask()) != null) { //上锁保证原子性操作 w.lock(); //通过线程池状态来判断进行是否进行中断 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //否则执行以下前置操作,默认是空实现 beforeExecute(wt, task); Throwable thrown = null; try { //执行任务(或者runnable/callable接口的任务类) task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //任务完成后进行一些后置任务,默认空实现 afterExecute(task, thrown); } } finally { //执行完一个任务后,进行相关操作,对应的工作线程对象完成的任务书+1,并且解锁 task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { //最后进行把工作线程对象移除工作线程集合操作。 processWorkerExit(w, completedAbruptly); } } private Runnable getTask() { //默认没有实际限制 boolean timedOut = false; // Did the last poll() time out? for (;;) { //获取线程池状态和存活线程数的结合体32位二进制 int c = ctl.get(); //获取线程池状态 int rs = runStateOf(c); // 判断是否处于接收任务和处理任务状态并且任务队列非空,如果是空则工作线程数-1,返回结束,在runwork方法后续会把该工作线程对象直接移出集合。 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } //获取工作线程的数量 int wc = workerCountOf(c); // 判断是否有时间限制或超过核心数 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //1.当前工作线程数超过了最大线程数并且任务队列也为空了则通过cas修改当前存活的工作线程数-1 //2.当超时并且当前工作线程数>1或者任务队列为空,也进行工作线程数修改-1 //如果通过cas修改成功则直接返回null,移除工作线程丢给上级操作 //否则一直循环到修改成功。 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } //如果以上条件都通过则开始从任务阻塞队列获取任务来执行 try { //有两种窃取任务的方式,一种是有时间限制的,一种是没有,从任务阻塞队列获取任务的过程这里就不说了。 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //如果获取成功,则返回给前面的工作线程执行 if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
到这里主要方法以及分析完成其他。
接着分析一下Executors线程池工具类里面提高的四大线程池
首先通过Executors来创建线程池在阿里开发者手册里面是极度不推荐的,原因是
-
FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
-
CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
下面来看一下四种创建demo
-
newFixedThreadPool(int n)-创建固定大小的线程池
-
newScheduledThreadPool(int n)-创建一个定时器线程池,线程可复用
public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); System.out.println(executorService); executorService.scheduleAtFixedRate(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }, 0, 500, TimeUnit.MILLISECONDS); }
-
newSingleThreadExecutor()-创建只有一个线程的线程池
public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); System.out.println(executorService); for (int i = 0; i < 10; i++) { executorService.execute(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); } TimeUnit.SECONDS.sleep(80); }
-
newCachedThreadPool()-创建不限线程数上限的线程池,任何提交的任务都立即执行
public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); System.out.println(executorService); for (int i = 0; i < 5; i++) { executorService.execute(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); } System.out.println(executorService); TimeUnit.SECONDS.sleep(80); System.out.println(executorService); }
-
接着看一下四种线程池的构造方法就知道为什么会出现oom
//================newFixedThreadPool========= public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); } //================SingleThreadExecutor========= public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
从上述两种线程池的构造函数可以看到,底层都是通过ThreadPoolExecutor来创建的,只是帮我们封装了一部分参数,但整因为这样,我们可以看到上面两种线程池只能手动设置线程数和线程工厂,但是一个线程池一次可以装载多少个任务是由阻塞队列+最大线程数来决定的,虽然我们控制了最大线程数,但是我们无法控制阻塞队列,因此阻塞队列可以无限装载任务,从而导致OOM异常。
接着再看下面两种线程池:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
从上面两种构造方法可以看出来,这两种线程池更离谱了,连最大线程数都无法手动设置,也会出现oom
总结:四种线程池底层都是通过ThredPoolExecutor类来创建线程的,只是这几种线程池的阻塞队列不一样,因此我们可以手动通过ThreadPoolExecutor来创建线程池,而且还可以手动指定最大线程数,以及阻塞队列的大小,可以有效防止出现OOM