首先,一个线程的线程的创建和销毁都需要消耗资源,特别是当线程中执行的是比较简单的任务时,大部分系统资源花费在线程的创建和销毁上,把过多的资源都用在了准备工作上,这显然不是我们想要的。可见,“每任务每线程”的做法并不合理。从另一个角度看,每个线程的创建和销毁过程都是一样的,同样的过程为什么我们要重复做那么多次呢?于是就产生了线程池的思想。
线程池把线程复用,一个线程执行完当前任务后并不马上销毁,而是从任务队列中取出一个任务继续运行。这种做法提高了线程的利用率,也减少了系统开销。
1.8中有一个线程池的顶级接口Executor,该接口很接单,只有一个execute()函数,接受Runnable参数
public interface Executor {
void execute(Runnable command);
}
关于线程池相关类和接口的关系,可见下图:
Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),
ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法
AbstractExecutorService:ExecutorService执行方法的默认实现
ScheduledExecutorService:一个可定时调度任务的接口
ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池
ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。
ForkJoinPool:将大任务分解成若干个小任务,当小任务均执行结束后,将任务做一个整合。
先来看看线程池的工作流程:
(图片取自https://www.cnblogs.com/superfj/p/7544971.html)
对于线程池ThreadPoolExecutor,其构造函数中包含了多个参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
时才有用, 超过这个时间的空闲线程将被终止;DefaultThreadFactory
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
综合上面的流程图和构造函数,线程池的工作原理是:当前线程数小于corePoolSize时,每提交一个任务都会创建一个新的线程来执行这个任务。当创建的线程数达到corePoolSize时如果再次提交任务,在任务将会放入到阻塞队列中。当阻塞队列中的任务已满时,如果再向线程池提交任务,则先检查corePoolSize和线程池最大允许线程数maximumPoolSize,如果核心线程数小于最大线程数,则继续创建线程执行任务;如果当前线程数已经大于等于最大线程数,则根据handler采取相应的饱和处理策略。
根据不同的应用场景,JDK1.8提供了下面几种不同的线程池,线程池主要通过一个Executors类的静态方法获得。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
newFixedThreadPool是一个具有固定线程数的线程池,传入一个nThreads参数,并且令核心线程数等于最大线程数,即corePoolSize=maximumPoolSize=nThreads。这样每当线程数达到核心线程数后就不会再继续创建线程,也不会有因为空闲而被终止的线程,此时的keepAliveTime参数不起作用。当前线程都在运行时,新提交的任务会一直提交到阻塞队列中,此时使用的是LinkedBlockingQueue,存储的任务数目可达到Integer.MAX_VALUE,可视为无界队列。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
缓存线程池,核心线程数corePoolSize=0,最大线程数为int最大值Integer.MAX_VALUE,线程空闲时间为60秒。阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列,他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时有大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
固定线程数目始终只有1的线程池,使用LinkBlockingQueue阻塞队列,单一的线程可保证任务按提交的顺序运行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
一个具有基础线程数corePoolSize且具有缓存功能的线程池,多余的线程一旦空闲下来就会被终止,能在指定时间执行任务。
ExecutorService提供了shutDown()和shutDownNow()两个函数来关闭线程池,底层还是通过逐个调用线程的interrupt()函数来实现中断线程从而关闭线程池的。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//上锁
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
shutdown函数会把线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();//队列中还未执行的任务
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;//返回还未执行的任务
}
shutdownNow方法会先将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法,并把工作队列中尚未来得及执行的任务清空到一个List中返回,getTask()方法返回null,从而线程退出 。但是ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
参考:
https://www.cnblogs.com/MOBIN/p/5436482.html
https://www.cnblogs.com/superfj/p/7544971.html
https://blog.csdn.net/programmer_at/article/details/79799267