前言
线程池是Java
中应用较多的并发框架,正当应用线程池,能够:升高资源耗费
,进步响应速度
,进步线程的可管理性
。本篇文章为《Java并发编程的艺术》
第9章的学习笔记,依据原文作者的编写思路,顺次对线程池的原理
,线程池的创立
,线程池执行工作
和敞开线程池
进行了学习和总结。最初会给出一个线程池的实现案例,该案例为之前在某微信公众号上看到的线程池实现实战,曾经无奈讲究其出处,原案例对于线程池的敞开存在缺点,我对其进行了一些修改和阐明,分享进去一起学习。
注释
一. 线程池的原理
当一个工作提交到线程池ThreadPoolExecutor
时,该工作的执行如下图所示。
- 如果以后运行的线程数小于corePoolSzie(外围线程数),则创立新线程来执行工作(须要获取全局锁);
- 如果以后运行的线程数等于或大于corePoolSzie,则将工作退出
BlockingQueue
(工作阻塞队列); - 如果
BlockingQueue
已满,则创立新的线程来执行工作(须要获取全局锁); - 如果创立新线程会使以后线程数大于maximumPoolSize(最大线程数),则回绝工作并调用
RejectedExecutionHandler
的rejectedExecution()
办法。
因为ThreadPoolExecutor
存储工作线程应用的汇合是HashSet
,因而执行上述步骤1和步骤3时须要获取全局锁来保障线程平安,而获取全局锁会导致线程池性能瓶颈,因而通常状况下,线程池实现预热后(以后线程数大于等于corePoolSize),线程池的execute()
办法都是执行步骤2。
二. 线程池的创立
通过ThreadPoolExecutor
可能创立一个线程池,ThreadPoolExecutor
的构造函数签名如下。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
通过ThreadPoolExecutor
创立线程池时,须要指定线程池的外围线程数,最大线程数,线程保活工夫,线程保活工夫单位和工作阻塞队列,并按需指定线程工厂和饱和回绝策略,如果不指定线程工厂和饱和回绝策略,则ThreadPoolExecutor
会应用默认的线程工厂和饱和回绝策略。上面别离介绍这些参数的含意。
参数 | 含意 |
---|---|
corePoolSize | 外围线程数,即线程池的根本大小。当一个工作被提交到线程池时,如果线程池的线程数小于corePoolSize,那么无论其余线程是否闲暇,也需创立一个新线程来执行工作。 |
maximumPoolSize | 最大线程数。当线程池中线程数大于等于corePoolSize时,新提交的工作会退出工作阻塞队列,然而如果工作阻塞队列已满且线程数小于maximumPoolSize,此时会持续创立新的线程来执行工作。该参数规定了线程池容许创立的最大线程数 |
keepAliveTime | 线程保活工夫。当线程池的线程数大于外围线程数时,多余的闲暇线程会最大存活keepAliveTime的工夫,如果超过这个工夫且闲暇线程还没有获取到工作来执行,则该闲暇线程会被回收掉。 |
unit | 线程保活工夫单位。通过TimeUnit 指定线程保活工夫的工夫单位,可选单位有DAYS(天),HOURS(时),MINUTES(分),SECONDS(秒),MILLISECONDS(毫秒),MICROSECONDS(微秒)和NANOSECONDS(纳秒),但无论指定什么工夫单位,ThreadPoolExecutor 对立会将其转换为NANOSECONDS。 |
workQueue | 工作阻塞队列。线程池的线程数大于等于corePoolSize时,新提交的工作会增加到workQueue中,所有线程执行完上一个工作后,会循环从workQueue中获取工作来执行。 |
threadFactory | 创立线程的工厂。能够通过线程工厂给每个创立进去的线程设置更有意义的名字。 |
handler | 饱和回绝策略。如果工作阻塞队列已满且线程池中的线程数等于maximumPoolSize,阐明线程池此时处于饱和状态,应该执行一种回绝策略来解决新提交的工作。 |
三. 线程池执行工作
线程池应用两个办法执行工作,别离为execute()
和submit()
。execute()
办法用于执行不须要返回值的工作,submit()
办法用于执行须要返回值的工作。execute()
是接口Executor
定义的办法,submit()
是接口ExecutorService
定义的办法,相干类图如下所示。
ThreadPoolExecutor
对execute()
的实现如下。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
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);
}
else if (!addWorker(command, false))
reject(command);
}
AbstractExecutorService
对submit()
实现如下。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在execute()
办法中会依据以后线程数决定是新建线程来解决工作还是增加工作到工作阻塞队列中,而在submit()
办法中是将工作封装成RunnableFuture
而后再调用execute()
办法。
四. 敞开线程池
能够通过调用线程池的shutdown()
或者shutdownNow()
办法来敞开线程池。
shutdown()
办法会将线程池状态置为SHUTDOWN
,此时线程池不会再接管新提交的工作,闲暇的线程会被中断,当正在被执行的工作和工作阻塞队列中的工作执行完后线程池才会平安的敞开掉。
shutdownNow()
办法会将线程池状态置为STOP
,此时线程池不会再接管新提交的工作,所有线程会被中断,工作阻塞队列中的工作不再执行(这些工作会以列表模式返回),正在执行中的工作也会被尝试进行。
补充:上述中的闲暇线程能够了解为正在从工作阻塞队列中获取工作的线程,即没有在执行工作的线程。
五. 线程池实现实战
在ThreadPoolExecutor
中,存储Worker
(工作线程)的汇合为HashSet
,因而每次对Worker
汇合做操作时须要获取全局锁。在本线程池实现的实战中,将基于ConcurrentHashMap
实现一个ConcurrentHashSet
并作为存储Worker
的汇合。实现如下。
public class ConcurrentHashSet<T> extends AbstractSet<T> {
private final ConcurrentHashMap<T, Object> MAP = new ConcurrentHashMap<>();
//无实际意义,用于和Worker组成键值对存入ConcurrentHashMap
private final Object PRESENT = new Object();
//用于统计以后汇合中的Worker数量
private final AtomicInteger COUNT = new AtomicInteger();
@Override
public boolean add(T t) {
COUNT.incrementAndGet();
return MAP.put(t, PRESENT) == null;
}
@Override
public boolean remove(Object o) {
COUNT.decrementAndGet();
return MAP.remove(o) == PRESENT;
}
@Override
public Iterator<T> iterator() {
return MAP.keySet().iterator();
}
@Override
public int size() {
return COUNT.get();
}
}
再看一下ThreadPool
的字段。
public class ThreadPool {
//全局锁
private final ReentrantLock lock = new ReentrantLock();
//外围线程数量
private final int coreSize;
//最大线程数量
private final int maxSize;
//线程存活放弃工夫
private final long keepAliveTime;
//线程存活放弃工夫单位
private final TimeUnit unit;
//工作阻塞队列
private final BlockingQueue<Runnable> workQueue;
//线程工厂
private volatile ThreadFactory threadFactory;
//回绝策略
private volatile RejectedExecutionHandler handler;
//寄存线程池中的线程的汇合
private final Set<Worker> workers = new ConcurrentHashSet<>();
//线程池是否平安敞开标记
private final AtomicBoolean shutDown = new AtomicBoolean(false);
//线程池是否强制敞开标记
private final AtomicBoolean shutDownNow = new AtomicBoolean(false);
//提交到线程池中的工作总数
private final AtomicInteger taskNum = new AtomicInteger();
......
/**
* 获取线程工厂
*/
public ThreadFactory getThreadFactory() {
return threadFactory;
}
/**
* 更新线程池的回绝策略
* @param handler 回绝策略
*/
public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
if (handler == null) {
throw new NullPointerException();
}
this.handler = handler;
}
/**
* 更新线程池的线程工厂
* @param threadFactory 线程池
*/
public void setThreadFactory(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException();
}
this.threadFactory = threadFactory;
}
......
}
因为本实战仅须要实现线程池的根本简略性能,因而外围线程数量,最大线程数量,线程保活工夫和线程保活工夫单位一经指定,便无奈再被批改。再看一下构造函数。
public class ThreadPool {
......
/**
* 创立线程池,并应用默认的回绝策略和线程工厂
* @param coreSize 外围线程数
* @param maxSize 最大线程数
* @param keepAliveTime 线程保活工夫
* @param unit 线程保活工夫单位
* @param workQueue 工作阻塞队列
*/
public ThreadPool(int coreSize, int maxSize, int keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this.coreSize = coreSize;
this.maxSize = maxSize;
this.keepAliveTime = keepAliveTime;
this.unit = unit;
this.workQueue = workQueue;
threadFactory = Executors.defaultThreadFactory();
handler = new AbortPolicy();
}
/**
* 创立线程池,并应用默认的回绝策略
* @param coreSize 外围线程数
* @param maxSize 最大线程数
* @param keepAliveTime 线程保活工夫
* @param unit 线程保活工夫单位
* @param workQueue 工作阻塞队列
* @param threadFactory 线程工厂
*/
public ThreadPool(int coreSize, int maxSize, int keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this.coreSize = coreSize;
this.maxSize = maxSize;
this.keepAliveTime = keepAliveTime;
this.unit = unit;
this.workQueue = workQueue;
this.threadFactory = threadFactory;
handler = new AbortPolicy();
}
/**
* 创立线程池
* @param coreSize 外围线程数
* @param maxSize 最大线程数
* @param keepAliveTime 线程保活工夫
* @param unit 线程保活工夫单位
* @param workQueue 工作阻塞队列
* @param threadFactory 线程工厂
* @param handler 回绝策略
*/
public ThreadPool(int coreSize, int maxSize, int keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
this.coreSize = coreSize;
this.maxSize = maxSize;
this.keepAliveTime = keepAliveTime;
this.unit = unit;
this.workQueue = workQueue;
this.threadFactory = threadFactory;
this.handler = handler;
}
......
}
提供了三个构造函数,和ThreadPoolExecutor
相似,外围线程数量,最大线程数量,线程保活工夫,线程保活工夫单位和工作阻塞队列须要用户指定。上面看一下submit()
办法和execute()
办法。
public class ThreadPool {
......
/**
* 执行有返回值的工作
* @param callable 须要执行的工作,有返回值
*/
public <T> Future<T> submit(Callable<T> callable) {
if (callable == null) {
throw new NullPointerException();
}
FutureTask<T> futureTask = new FutureTask<>(callable);
execute(futureTask);
return futureTask;
}
/**
* 执行无返回值的工作
* @param runnable 须要执行的工作,无返回值
*/
public void execute(Runnable runnable) {
if (runnable == null) {
throw new NullPointerException();
}
//调用了shutDown()或者shutDownNow()办法后,此时线程池不承受新工作
if (shutDown.get() || shutDownNow.get()) {
return;
}
//工作总数加一
taskNum.incrementAndGet();
//如果以后线程数小于外围线程数,创立Worker(线程)并执行工作
if (workers.size() < coreSize) {
addWorker(runnable);
return;
}
//如果以后线程数大于等于外围线程数,则将工作退出工作阻塞队列
//如果工作阻塞队列已满,则offer()办法返回false示意增加失败
boolean offer = workQueue.offer(runnable);
if (!offer) {
//如果以后线程数小于最大线程数,则创立Worker来执行工作
if (workers.size() < maxSize) {
addWorker(runnable);
} else {
//如果以后线程数大于等于最大线程数,则执行回绝策略,并且工作总数减一
taskNum.decrementAndGet();
handler.rejectedExecution(runnable, this);
}
}
}
private void addWorker(Runnable runnable) {
Worker worker = new Worker(runnable);
workers.add(worker);
worker.getThread().start();
}
......
}
ThreadPool
的execute()
办法遵循第一大节中总结的线程池接管到一个新工作时的判断策略。addWorker()
办法会创立一个Worker
对象,而后将其增加到Worker
汇合中,因为应用了实现的线程平安的ConcurrentHashSet
作为Worker
汇合,因而该步骤不须要获取全局锁。Worker
类是ThreadPool
的外部类,其实现如下。
public class ThreadPool {
......
private final class Worker implements Runnable {
//要运行的初始工作(创立Worker时传入的工作)
private final Runnable firstTask;
//线程(通过线程工厂创立)
private final Thread thread;
public Worker(Runnable task) {
firstTask = task;
thread = getThreadFactory().newThread(this);
}
public Thread getThread() {
return thread;
}
public void close() {
thread.interrupt();
}
@Override
public void run() {
//创立Worker时会先执行初始工作
Runnable task = firstTask;
try {
//如果task为空,则从工作阻塞队列中获取工作
while (task != null || (task = getTask()) != null) {
try {
//执行工作
task.run();
} finally {
//每次工作执行结束,会将task置为null,而后循环从工作阻塞队列中获取工作
task = null;
taskNum.decrementAndGet();
}
}
} finally {
//从工作阻塞队列中获取工作为null时会跳出while循环并执行这里的开释线程逻辑
if (workers.remove(this)) {
//用于调用shutDown()办法后平安的敞开线程池
tryClose();
}
}
}
}
......
}
Worker
有两个字段:firstTask和thread。其中firstTask会被赋值为创立Worker
时传入的工作,示意Worker
的初始工作,因而一个Worker
总是会先执行初始工作,而后再去从工作阻塞队列中获取工作。thread会在创立Worker
时通过线程工厂创立,并且thread执行的工作为Worker
本身(因为Worker
实现了Runnable
接口,Worker
本身也是一个工作)。在创立Worker
后,Worker
中的线程thread便会启动,从而会执行Worker
的run()
办法,run()
办法的实现思路为先执行初始工作,而后循环调用ThreadPool
的getTask()
办法从工作阻塞队列中获取工作,如果获取工作失败(超过了线程保活工夫/线程被中断)则退出循环并执行开释Worker
的逻辑。
上面看一下getTask()
办法的实现。
public class ThreadPool {
......
private Runnable getTask() {
Runnable task;
//如果强制敞开标记为true,则不再从工作阻塞队列中获取工作
//如果平安敞开标记为true,且工作全副执行完,则不再从工作阻塞队列中获取工作
if (shutDownNow.get() || (shutDown.get() && taskNum.get() == 0)) {
return null;
}
try {
//同一时间只能有一个线程从工作阻塞队列中获取工作,其余线程进入阻塞状态
lock.lockInterruptibly();
if (workers.size() > coreSize) {
//如果以后线程数大于外围线程数,则线程从工作阻塞队列中获取工作时最多期待线程保活的工夫,超时则返回null
task = workQueue.poll(keepAliveTime, unit);
if (task == null) {
System.out.println(Thread.currentThread().getName() + " time out");
}
} else {
//如果以后线程数小于等于外围线程数,则线程从工作阻塞队列中获取工作时会始终阻塞直到获取到工作
task = workQueue.take();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted");
task = null;
} finally {
lock.unlock();
}
return task;
}
......
}
要了解getTask()
办法,须要联合ThreadPool
的shutDown()
办法和shutDownNow()
办法一起剖析。其实现如下。
public class ThreadPool {
......
/**
* 平安的敞开线程池,会期待以后正在执行的工作以及工作阻塞队列中的工作执行结束后再敞开线程池
*/
public void shutDown() {
shutDown.set(true);
tryClose();
}
/**
* 强制的敞开线程池,立刻中断所有线程,包含正在执行工作的线程
*/
public void shutDownNow() {
shutDownNow.set(true);
doClose();
}
private void tryClose() {
if (shutDown.get() && taskNum.get() == 0) {
//如果平安敞开标记为true且工作全副执行完,此时立刻中断所有线程
for (Worker worker : workers) {
worker.close();
}
}
}
private void doClose() {
if (shutDownNow.get()) {
//如果强制敞开标记为true,此时立刻中断所有线程
for (Worker worker : workers) {
worker.close();
}
}
}
}
Worker
通过getTask()
办法从工作阻塞队列获取工作分三种状况探讨。
第一种状况:shutDown()
办法和shutDownNow()
办法没有被调用,此时shutDown和shutDownNow均为false。该种状况下Worker
能够失常从工作阻塞队列中获取工作,因为应用了全局锁,所以在同一时间,只能有一个Worker
可能从工作阻塞队列中获取工作,其余Worker
进入阻塞状态。如果线程池的以后线程数大于外围线程数,那么获取到全局锁的Worker
会阻塞在工作阻塞队列的poll()
办法上,超过保活工夫还没获取到工作则间接返回null,从而会开释这个Worker
;如果线程池的以后线程数小于等于外围线程数,那么获取到全局锁的Worker
会始终阻塞在工作阻塞队列的take()
办法上直到获取到工作。
第二种状况:shutDown()
办法被调用,此时shutDown为true。如果还有未执行完的工作,那么Worker
能够失常从工作阻塞队列获取工作,同状况一。如果工作阻塞队列没有工作且没有正在执行的工作,那么shutDown()
办法调用时便会中断所有线程,在getTask()
办法中阻塞的Worker
会被全副唤醒并被开释掉,如果shutDown()
办法调用时工作阻塞队列没有工作然而存在正在执行的工作,那么期待最初一个工作执行完后会中断所有线程,在getTask()
办法中阻塞的Worker
会被全副唤醒并被开释掉。
第三种状况:shutDownNow()
办法被调用,此时shutDownNow为true。无论工作阻塞队列是否有工作以及无论是否有正在执行的工作,在shutDownNow()
办法调用时便会中断所有线程,如果线程是阻塞在工作阻塞队列中,那么线程会被唤醒并被开释掉,如果线程正在执行工作,那么也会尝试中断线程,执行完工作或者响应中断从工作中退出的线程再次从getTask()
办法获取工作时会间接获取到null(因为shutDownNow为true),从而被开释掉。特地留神,如果某个线程执行的工作是一个死循环并且无奈响应中断,那么这个线程永远不会被开释。
最初ThreadPool
提供一个size()
办法用于获取以后的Worker
数量,该办法仅用于测试。
public int size() {
return workers.size();
}
上面编写测试程序来测试ThreadPool
。
测试用例一:测试ThreadPool
的shutDown()
办法,创立一个ThreadPool
,外围线程数为2,最大线程数为4,工作阻塞队列大小为20,创立一个固定工作为打印数字0-2,每打印一次数字睡眠1秒。首先向ThreadPool
提交三个固定工作,期待4秒后执行shutDown()
办法,预期的后果应该为提交的三个工作都会执行完,并且工作执行完后阻塞在工作阻塞队列中的工作会被唤醒并被开释。测试代码如下。
public class ThreadPoolTest {
@Test
void testThreadPool_1() {
CountDownLatch countDownLatch = new CountDownLatch(3);
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(20);
ThreadPool threadPool = new ThreadPool(2,
4, 1000, TimeUnit.MILLISECONDS, workQueue);
Runnable task = () -> {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted from sleep");
}
countDownLatch.countDown();
};
for (int i = 0; i < 3; i++) {
threadPool.execute(task);
}
try {
Thread.sleep(4000);
threadPool.shutDown();
Thread.sleep(3000);
countDownLatch.await();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("Worker size: " + threadPool.size());
System.out.println("Task num: " + workQueue.size());
}
}
测试后果如下所示。
结果表明,一开始ThreadPool
别离创立了线程1和线程2来解决task1和task2,而后task3被增加到了工作阻塞队列,当线程1和线程2执行完工作后,线程2获取到了全局锁,从工作阻塞队列中获取到了task3,而线程1进入阻塞状态,此时调用shutDown()
办法,期待线程2执行完工作后,中断所有线程,此时线程1被唤醒而后被开释掉,最初ThreadPool
中线程数量和工作数量全副为0。
测试用例二:测试ThreadPool
的shutDownNow()
办法,创立一个ThreadPool
,外围线程数为2,最大线程数为4,工作阻塞队列大小为20,创立一个固定工作为打印数字0-2,每打印一次数字睡眠1秒。首先向ThreadPool
提交三个固定工作,期待2秒后执行shutDownNow()
办法,预期的后果应该为提交的三个工作都不会执行完,所有线程在shutDownNow()
办法被调用后会被中断并开释掉。测试代码如下。
public class ThreadPoolTest {
@Test
void testThreadPool_2() {
CountDownLatch countDownLatch = new CountDownLatch(2);
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(20);
ThreadPool threadPool = new ThreadPool(2,
4, 1000, TimeUnit.MILLISECONDS, workQueue);
Runnable task = () -> {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted from sleep");
}
countDownLatch.countDown();
};
for (int i = 0; i < 3; i++) {
threadPool.execute(task);
}
try {
Thread.sleep(2000);
threadPool.shutDownNow();
Thread.sleep(1000);
countDownLatch.await();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("Worker size: " + threadPool.size());
System.out.println("Task num: " + workQueue.size());
}
}
测试后果如下所示。
结果表明,一开始ThreadPool
别离创立了线程1和线程2来解决task1和task2,而后task3被增加到了工作阻塞队列,而后在线程1和线程2执行工作的过程中,调用了shutDownNow()
办法,因为task1和task2可能响应中断并退出工作,所以线程1和线程2会从工作中退出而后被开释掉,工作阻塞队列中的task3也不会被执行,最初ThreadPool
中线程数量为0,工作数量为1。
测试用例三:创立一个ThreadPool
,外围线程数为2,最大线程数为4,工作阻塞队列大小为1,线程保活工夫为1秒,创立一个固定工作为打印数字0-2,每打印一次数字睡眠1秒。首先向ThreadPool
提交五个固定工作,而后期待ThreadPool
执行这些工作,预期的后果应该为在所有工作执行完后,ThreadPool
中的线程数应该为2(外围线程数),工作数应该为0。测试代码如下。
public class ThreadPoolTest {
@Test
void testThreadPool_3() {
CountDownLatch countDownLatch = new CountDownLatch(5);
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1);
ThreadPool threadPool = new ThreadPool(2,
4, 1000, TimeUnit.MILLISECONDS, workQueue);
Runnable task = () -> {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted from sleep");
}
countDownLatch.countDown();
};
for (int i = 0; i < 5; i++) {
threadPool.execute(task);
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("Worker size: " + threadPool.size());
System.out.println("Task num: " + workQueue.size());
}
}
测试后果如下所示。
结果表明,一开始ThreadPool
创立了线程1和线程2来解决task1和task2,而后task3被增加到了工作阻塞队列,而后创立了线程3和线程4来解决task4和task5,在工作执行完后,线程4获取到了全局锁,从工作阻塞队列中获取到了task3继续执行,此时线程1和线程2因为在线程保活工夫内没有获取到工作来执行,被开释掉,最初ThreadPool
中线程数量为2,工作数量为0。
总结
线程池的应用次要聚焦于线程池的参数的配置,而要正当的配置线程池的参数,则须要对线程池的原理有肯定的理解,如果可能本人写一个线程池并实现基本功能,对了解线程池的原理大有益处。
发表回复