线程池的存在就是为了合理的利用线程,减少不必要的线程频繁创建销毁,提升程序性能。
在最初接触线程池的时候,我们都知道Executors工具类,可以提供创建不同类型的线程池,包括提供单个线程的线程池、固定数量线程的线程池以及缓存线程池等;
public static ExecutorService newFixedThreadPool(int nThreads){}
public static ExecutorService newSingleThreadExecutor(){}
public static ExecutorService newCachedThreadPool(){}
而这些工具创建线程池,最终都是创建了一个ThreadPoolExecutor对象,该对象提供了多个重载的构造器,通过搭配不同参数实现上述的线程池需求。
ThreadPoolExecutor对象虽然提供了多个重载的构造器,但最终都是在调用同一个构造器:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中各个参数的意义:
corePoolSize:核心线程数,当线程池待执行任务队列没满时,线程池最多提供的线程数;
maximumPoolSize:最大线程数,当线程池待执行任务队列已满时,会尝试创建更多线程来处理任务,线程总数上限就是maximumPoolSize;
keepAliveTime:如果线程池中的线程数超过了核心线程数,则如果有空闲时间超过keepAliveTime的线程,就会被回收;
unit:keepAliveTime的时间单位;
workQueue:待执行的任务队列,当线程池繁忙且无法创建更多核心线程来处理任务时,任务会被添加到待执行队列,排队等候被执行;
threadFactory:线程工厂,默认是Executors工具类中的静态内部类DefaultThreadFactory,它实现了ThreadFactory接口,该接口只定义了一个方法:
Thread newThread(Runnable r);
DefaultThreadFactory实现代码如下,总结起来就是统一创建同一优先级的非守护线程:
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;
}
handler:拒绝策略,当线程池没有额外的线程处理新的任务,且待执行队列也满了的时候,或者干脆线程池已经被关闭,则新的任务就只能被拒绝,RejectedExecutionHandler是一个接口,该接口定义了如下方法:
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
ThreadPoolExecutor类中提供了几个默认的实现了RejectedExecutionHandler的静态内部类,每个拒绝策略都有自己的特点,具体包括如下几种拒绝策略:
AbortPolicy:简单粗暴型,直接叫你滚,抛出异常,这也是默认的拒绝策略:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
CallerRunsPolicy:兢兢业业型,你没空做,那我自己做:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
DiscardPolicy:高冷型,完全不搭理你:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
DiscardOldestPolicy:喜新厌旧型,丢弃掉待执行队列中最老的任务,添加新的任务:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
如果以上几种类型你都不喜欢,那么你可以自己实现RejectedExecutionHandler接口,口味自调。
清楚了以上的参数,那使用线程池就很简单了,一个很简单的例子如下:
public class ThreadPoolTest {
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
pool.execute(()->{
System.out.println("task execute");
});
pool.shutdown();
}
}
关于上面这个简单的案例,我们分三步看:
1、线程池的创建,这个就是利用ThreadPoolExecutor的构造器实例化对象,参数在上面都已经详细介绍了,此处没什么再可说的了;
2、execute提交任务,总体逻辑如下图所示:
3、shutdown,关闭线程池,说到这里,要说下线程池的几个状态了
RUNNING:线程池创建后即为该状态,可以执行队列中的任务也可以接收并处理新的任务;
SHUTDOWN:当调用shutdown()方法后,线程池进入该状态,此状态下线程池不再接收新的任务,如果继续提交任务,则执行拒绝策略,但是会继续执行队列中的任务;
STOP:当调用shutdownNow()方法后,线程池进入该状态,此状态下线程池不再接收新的任务,也不再处理队列中的任务,而且会尝试中断执行中的任务;
TIDYING:当线程池中所有任务都结束,且线程也全部被回收,则进入TIDYING状态;
TERMINATED:terminated()方法执行后,线程池彻底终结;
线程池中的线程是如何启动并回收的呢?
线程池中的每个线程是对应一个Worker实例,在创建完后,会循环处理提交的任务,在上文中的图中有所体现:
Worker的run()方法调用了ThreadPoolExecutor的runWoker方法:
public void run() {
runWorker(this);
}
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);
}
}
runWorker中用了个while循环处理任务,只要while循环不退出,则当前Worker线程就一直运行,从源码中可以看出,退出条件是task为null,也就是getTask()这个方法是控制Worker生命周期的关键方法,其源码如下:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
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;
}
}
}
1、正常情况下,会采用阻塞的方式(即take()方法)从待执行队列获取任务,即便被interrupt,也会再次循环阻塞获取,这样必然能保证返回可执行的任务,则worker线程不会被回收;
2、线程池如果进入SHUTDOWN状态,且待执行队列中的数据也被消费完了,则返回null,这样就保证了所有的worker线程会结束运行,从而被回收;
3、线程池如果进入了STOP状态,则不管待执行队列有没有任务,都会立即返回null,则worker线程结束;
4、如果当前worker线程总数超过了指定的核心线程数,则从队列获取数据时,使用超时阻塞的方式(即poll(long timeout, TimeUnit unit)方法)获取,超时时间即keepAliveTime,超时后如果没获取到任务,则线程空闲时间达到阈值,返回null,结束worker线程;
5、如果配置了了核心线程数也需要超时回收,则获取任务时同样采取的是超时阻塞方式,同第4点;