在java中,使用线程来执行异步任务。java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建和销毁将消耗大量的计算资源。同时,为每个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用崩溃。因此线程池就诞生了。
相关类和接口
与线程池相关的主要有以下的类和接口:
- Executors 工厂类,里面提供许多封装好的创建不同类型的线程池的方法
- Executor 是一个接口,它将任务的提交和任务的任务的执行分离开来。
- ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟之后执行命令,或者定期执行命令。它比Timer更加灵活,功能更加强大
- Future接口和实现Future接口的实现类FutureTask类,代表异步计算的结果。
- Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行
常用类ThreadPoolExecutor
ThreadPoolExecutor是AbstractExecutorService的实现类有四个构造方法。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
通过上面的构造函数,可以看到其实前面三个构造函数都是调用第四个构造函数的。下面解释第四个构造函数的每个变量的意义。
- corePoolSize : 线程池中核心线程的数量。在创建线程池之后,默认情况下线程池中并没有任何线程存在的,等待有任务到达之后线程池才会去创建线程。除非在初始化线程池之后调用了prestartCoreThread()或者prestartAllCoreThreads()方法来提前将线程创建启动起来。开发人员可以根据自己的经验或者实际情况去设置corePoolSize的值。
- maximumPoolSize :当前线程池中能够生成的最大线程数量。
- keepAliveTime :表示没有任务执行的时候 ,线程最多能够维持多久。默认情况下,只有当线程池中的线程数量大于corePoolSize的时候,这个参数值才会起作用。也就是说当线程池中的线程数量大于corePoolSize的时候,如果一个线程空闲时间达到keepAliveTime之后就会终止这个空闲线程,直到线程数量小于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean),这个参数也会起作用,直至线程池中线程的数量为0.
- TimeUnit : 和上面的keepAliveTime这个参数对应。就是字面意思时间单位值。是一个枚举类。
- workQueue:字面意思就是阻塞队列。用来存储等待执行的任务。可以选择以下几个值
ArrayBlockingQueue
SynchronousQueue
LinkedBlockingQueue
PriorityBlockingQueue
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列。此队列按照FIFO(先进先出)原则对元素进行排序。
SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作完成,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
LinkedBlockingQueue:一个基于链表结构的阻塞队列。此队列按照FIFO排序元素,吞吐量比ArrayBlockingQueue要高。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
- threadFactory:线程工厂,主要用来创建线程。
- handler :饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略提交新的任务。这种策略默认情况下是AbortPolicy。有四个值可供选择
ThreadPoolExecutor中相关属性和方法
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
Ctl
记录了当前线程池的状态和线程池中线程的个数。一个32位的AtomicInteger数字代表两个意义。32位数中,高三位是表示线程池的状态,低29位表示线程池中线程的数量。
线程池的各种状态
RUNNING : 高3位为111,此状态的线程池,能够接受新的任务,并且可以处理阻塞队列里面等待执行的任务。
SHUTDOWN : 高3位为000,此状态的线程池不接受新的任务,但是可以处理阻塞队列里面未处理完的任务。
STOP : 高三位为001,此状态的线程池,不会接受新的任务,也不会处理阻塞队列里面的任务。会去停止正在执行的任务。
TIDYING: 高3位为010,所有的任务都已经终止。
TERMINATED:高三位为011,terminated()方法已经执行完成。
线程池的状态量值维持一个大小关系RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED。关于线程池的状态有一个总体规则,只有在RUNNING状态下才可以接受新的任务,只有在RUNNING和SHUTDOWN状态能够处理阻塞队列里面的任务,其他任何状态下都不可处理阻塞队列里面的任务。
线程池中各个状态之间的转换(从上到下 从左到右的)
与ctl和线程池相关的几个函数
//获取线程池的当前状态,也就是前三位
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取当前线程池中线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//设置ctl的值 rs 为线程池的状态 wc为线程池中线程的数量
private static int ctlOf(int rs, int wc) { return rs | wc; }
FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池,适用于需要保证顺序执行各个任务;并且在任意时间点,不会有多个线程活动的应用场景。在Executors中提供了2个方法来创建FixedThreadPool
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);
}
FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool是指定的参数 nThreads。
当线程中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后,多余的线程将被终止。这里将keepAliveTime设置为0L,意味着多余的线程会被立即终止。FixedThreadPool的execute()方法的运行示意图如下
具体说明如下:
1) 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
2)在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue.
3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行
SingleThreadExecutor
SingleThreadExecutor是使用单个worker线程的Executor。创建方法:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor中的corePoolSize和maximumPoolSize被设置为1.其他参数与FixedThreadPool相同。SingleThreadExecutor的执行示意图如下
与FixThreadPool不相同的是,自始至终可执行的线程只有一个。
CachedThreadPool
CachedThreadPool是一个会根据需要创建新线程的线程池。创建CachedThreadPool的方法如下
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);
}
CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但是maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程,极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
CachedThreadPool的execute()方法执行示意图如下:
1)首先执行SynchronousQueue.offer(Runnable task).如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll
(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲的线程执行,execute方法执行完成,否则执行步骤 2)
2)当初始化maximumPool为空,或者maximumPool当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,
TimeUnit.NANOSECONDS)。这种情况下,步骤1)将失败。此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。
3)在步骤2)中新创建的线程将执行完成后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在Synchronous中等待60秒。如果60秒内,主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务,否则,这个空闲线程将终止。SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程对应的移除操作,反之亦然。
线程池中任务的执行
execute(Runnable command)函数
public void execute(Runnable command) {
if (command == null) //代码段1
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //代码段2
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //代码段3
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //代码段3-1
reject(command);
else if (workerCountOf(recheck) == 0) //代码段3-2
addWorker(null, false);
}
else if (!addWorker(command, false)) //代码段4
reject(command);
}
线程池中执行任务execute方法解读:
1.代码段1中,首先检查传入的任务command是否为null ,如果为null,就抛出异常。否则继续执行。
2.代码段2,获取ctl值,然后根据上文提到的函数,检查当前线程池中的线程数量小于corePoolSize的时候,就直接开启一个新的线程执行任务,这时候要检查是否将新的工作线程添加成功,如果成功就直接返回,失败则执行c= ctl.get()重新获取ctl的值。
3.如果代码段执行到代码段3 ,则说明线程池中的线程数量>=corePoolSize,此时应该检查线程池是否处于运行状态并且将当前的任务放入阻塞队列中.在代码段3-1开始再次获取ctl的值,检查此时线程池若不是运行状态,则不能接受新的任务,执行remove操作,如果remove成功,则执行reject方法处理任务。代码段3-2中有2种情况进入,则创建一个空的线程执行当前的command任务。
4.代码段4中,如果核心线程满了,并且阻塞队列也满了,就直接创建新的非核心线程来执行新的任务,当非核心任务addWork(command,core=false) 添加失败的时候,就执行reject方法处理任务。
在上述execute(Runnble command)中最主要的添加执行任务操作位addWorker(Runnable,Boolean)方法。
addWorker(Runnable,boolean)
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
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 {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
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;
}
在addWorker方法中主要做了以下几件事情:
1.首先是检查线程池的状态,或者线程池中线程的数量,或者ctl,判断是否能够增加线程计数。等等这些条件不满足时候,任务添加不成功。
2.将自己传入的Runnable任务通过Worker来进行封装,添加到works集合中。
3.添加成功之后更新largePoolSize(线程池中创建过的最大线程数量),最后启动任务。
PS:成功添加worker时线程池必须处于两种状态中的一种:
a.线程池处于RUNNING状态
b.线程池处于SHUTDOWN状态,且创建线程的时候没有传入新的任务(此状态下不接收新任务),且任务队列不为空(此状态下,要执行完任务队列中的剩余任务才能关闭)
在上面代码中有这么一段
if (workerAdded)
{
t.start();
workerStarted = true;
}
当任务添加成功之后 直接调用的start方法,这里是调用线程池的execute(Runnable command)方法直接执行任务。在ThreadPoolExecutor中还可以通过submit方法提交任务。只不过这个submit方法在AbstractExecutorService方法中
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在submit方法中,无论传递的是Runnable 还是callable对象,最后都调用newTaskFor封装成FutureTask对象,然后提交当前任务到线程池中执行,execute和submit方法明显的区别就是submit返回一个Future对象。
简要介绍一下FutureTask
FutureTask的几种状态
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
上面只讲解了当线程池中的线程数量少于核心线程的时候,直接new新的线程来执行任务,但是当线程池中的线程大于核心线程数量小于最大线程数的时候,这时候就将新加入的线程放入阻塞队列workQueue当中,而我们的线程池是如何唤醒在阻塞队列中的线程的呢。再来看看addWork中的代码
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); //注释一 产生一个Worker对象
final Thread t = w.thread; //这里生成一个Thread对象
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
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) { //注释二 添加Worker对象成功之后,直接启动work
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
看到上面的中文注释一和注释二产生了一个Thread对象t = w.thread,最后调用t.start将任务启动执行起来。在来看看Worder中的Thread是怎么产生的,在Worker构造函数值初始化了Thread
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
是通过ThreadFactory产生Thread对象的,而ThreadPoolExecutor当中默认的ThreadFactory是通过下面的方法生成的
Executors.defaultThreadFactory()
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
最后看newThread方法中传递的是Runnable对象,而在我们的Worker的构造函数种,默认传递的是Worker自己,那也就是。结合上面的,其实最后当执行我们的t.start方法,其实调用的是Worker的run方法,最后调用的是runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
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 {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
getTask方法其实就是一直在取workQueue队列里面的任务,在执行完一个任务之后,就会从workQueue中去取,取到了 就执行取到的任务,这样就会源源不断的从workQueue中取出任务并执行。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池停止了,就将计数器置为0
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// timed变量用于判断是否需要进行超时控制。
// allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
// 对于超过核心线程数量的这些线程,需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//poll方式取任务的特点是从缓存队列中取任务,最长等待keepAliveTime的时长,取不到返回null
//take方式取任务的特点是从缓存队列中取任务,若队列为空,则进入阻塞状态,直到能取出对象为止
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
从以上代码可以看出,getTask()的作用是
1.如果当前活动线程数大于核心线程数,当去缓存队列中取任务的时候,如果缓存队列中没任务了,则等待keepAliveTime的时长,此时还没任务就返回null,这就意味着runWorker()方法中的while循环会被退出,其对应的线程就要销毁了,也就是线程池中少了一个线程了。因此只要线程池中的线程数大于核心线程数就会这样一个一个地销毁这些多余的线程。
2.如果当前活动线程数小于等于核心线程数,同样也是去缓存队列中取任务,但当缓存队列中没任务了,就会进入阻塞状态,直到能取出任务为止,因此这个线程是处于阻塞状态的,并不会因为缓存队列中没有任务了而被销毁。这样就保证了线程池有N个线程是活的,可以随时处理任务,从而达到重复利用的目的。
注意:“核心线程”、“非核心线程”是一个虚拟的概念,是为了方便描述而虚拟出来的概念,在代码中并没有哪个线程被标记为“核心线程”或“非核心线程”,所有线程都是一样的,只是当线程池中的线程多于指定的核心线程数量时,会将多出来的线程销毁掉,池中只保留指定个数的线程。那些被销毁的线程是随机的,可能是第一个创建的线程,也可能是最后一个创建的线程,或其它时候创建的线程。一开始我以为会有一些线程被标记为“核心线程”,而其它的则是“非核心线程”,在销毁多余线程的时候只销毁那些“非核心线程”,而“核心线程”不被销毁。这种理解是错误的。
在ThreadPoolExecutor 的execute方法中,最后一个分支是 addWorker(command, false),添加非核心线程,就是线程数大于corePoolSize并且阻塞队列满的时候,就创建非核心线程执行新的任务。这就造成了一种,阻塞队列里面的任务,只能由核心线程去完成,后面的创建的非核心线程,可能后来先执行了。可能java设计者就是这么设计的吧。当并发任务重,核心线程已经处理不过来了,所以要新建非核心线程来执行任务,直到达到允许的最大(maxPoolSize)线程数,如果这时候还有任务发过来就会拒绝任务,不再新建线程了。进队列的任务不一定是被有序执行的.
上述代码创建了一个核心线程数为 2,最大线程数为 10,等待队列长度为 2 的线程池。执行效果如下:
解释说明:
- 1 处表示线程数量已经达到 corePoolSize;
- 2 处表明等待队列已满;
- 3 处会创建新的线程执行任务。
将上面非核心线程的代码稍微修改一下,如下:
修改最大线程数为 3,并提交 6 次任务给线程池,执行效果如下:
程序会报异常 RejectedExecutionException,拒绝策略是线程池的一种保护机制,目的就是当这种无节制的线程资源申请发生时,拒绝新的任务保护线程池。
submit和execute的区别
在使用线程池的时候,会有两类执行任务的方式。一类是调用execute方法,一类是调用submit方法
public void execute(Runnable command) //submit在父类 AbstractExecutorService中实现 public <T> Future<T> submit(Callable<T> task) public Future<?> submit(Runnable task) public <T> Future<T> submit(Runnable task, T result)
主要区别:
1.execute的参数只能是Runnable对象,而submit可以传递Callable,Runnable。
2.出错处理方式不一样。咋不一样,看下面的例子
使用submit方法提交一个任务:
public static void main(String args[]){
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,30,0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
Future<?> future = poolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(1/0);
System.out.println("调用submit方法");
}
});
}
按理说我们打印1/0这个肯定会存在问题,但是我们运行之后,控制台啥输出都没有,将我们把submit修改为execute方法之后,有下面的错误输出:
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.android.Test.ThreadPoolTest$1.run(ThreadPoolTest.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
这个应该是预期的错误。但是submit为啥没有输出,这就是submit和execute的错误处理方式不一样.当我们将代码修改为如下形式,调用了future.get方法之后,就会得到上面的错误:
public static void main(String args[]){
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,30,0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
Future<?> future = poolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(1 / 0);
System.out.println("调用submit方法");
}
});
try{
System.out.println("future ="+future.get());
}catch (Exception e){
e.printStackTrace();
}
}
所以我们需要在submit任务之后,在返回值之中去处理错误。
当然我们也可以统一处理execute执行任务的错误。可以借助 guava 并发处理相关的类,统一处理错误:
// 1. Use Guava in your implementation only: implementation("com.google.guava:guava:29.0-jre") // 2. Use Guava types in your public API: api("com.google.guava:guava:29.0-jre") // 3. Android - Use Guava in your implementation only: implementation("com.google.guava:guava:29.0-android") // 4. Android - Use Guava types in your public API: api("com.google.guava:guava:29.0-android")
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 30, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5), new ThreadFactoryBuilder().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
System.out.println("e.message="+e.getMessage());
}
}).build(), new ThreadPoolExecutor.DiscardPolicy());
poolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(1 / 0);
System.out.println("调用submit方法");
}
});
我们设置自己的错误处理setUncaughtExceptionHandler,让后在uncaughtException方法中做错误处理,上面的代码执行完后的输出为:
e.message=/ by zero
线程池的相关回到方法
线程池的相关回调方法如下,我们需要自定义线程池的时候 ,去重写这些方法,就可以在这些方法中,做一些我们需要处理的事情。
protected void beforeExecute(Thread t, Runnable r) //线程执行之前调用
protected void afterExecute(Runnable r, Throwable t) { } //线程执行之后调用
protected void terminated() { } //线程池终止的时候调用
关闭线程池
关闭线程池可以调用shutdownNow和shutdown两个方法来实现。
shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。