一、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建固定数量的核心线程,使用LinkedBlockingQueue作为任务队列,没有限制容量,有内存溢出的风险。
二、newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
等价于Executors.newFixedThreadPool(1)。
三、newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
没有核心线程,都是非核心线程,线程的允许空闲时间为60秒,阻塞队列使用SynchronousQueue。
线程池提交任务的流程:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 由于corePoolSize为0,这个if体的逻辑肯定不会执行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// SynchronousQueue是否能offer成功取决于是否有线程在poll
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果没有线程在poll,则创建非核心线程执行任务
else if (!addWorker(command, false))
reject(command);
}
由于newCachedThreadPool使用的是SynchronousQueue,导致情况比较复杂。
取决于任务的提交频率和任务的执行时间。
假如任务t1时间提交一次,任务执行时间为t2。
- 当t1-t2大于等于60秒,比如80秒提交一次,每次执行10秒,那么线程池的线程数不超过1,每来一个任务创建一个线程,执行完任务后闲置60秒被回收。此时已经失去了线程池的意义,线程无法复用。
- 当t1-t2小于60秒,那么线程闲置不会超过60秒,线程可以复用。
2.1 当t1-t2小于60秒,但大于0秒时,线程池会稳定在只有一个线程。
2.2 只有当t1-t2小于0秒时,即t1<t2时,线程池才会创建多个线程。
那么会不会创建大量的非核心线程导致内存溢出?
基本不会,这种情况,除非是t2特别特别大,t2>>>t1。
举个例子:
假如,1秒提交一次,一次执行1小时,线程池最终会稳定在多少个线程呢?
1小时等于3600秒。
那么前3600秒,由于之前的线程firstTask都还没执行完,所以每来一个任务就创建一个线程。当时间来到第3601秒,第一个创建出来的线程的firstTask执行完了,可以复用了,第3602秒,第二个创建出来的线程的firstTask执行完了,可以复用了,周而复始。
所以结论是,1小时后,线程池的线程数量会稳定在3600个,不会创建新的,旧的也不会被回收。
不适宜场景:间歇性的高频任务提交。短时间创建大量线程,然后又长时间无任务导致刚刚创建的大量线程被回收。
网友评论