线程池 (Thread Pool) 是一种基于池化思想帮助我们管理线程而获取并发性的工具,经常出现在多线程服务器中,如 MySQL。线程池的实现思路:提前创建好多个线程,让这些线程处于就绪状态来提高系统响应速度,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。
内存池 (Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
连接池 (Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
实例池 (Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
package com.java.forkjoinpool;
import java.util.concurrent.CompletableFuture;
/**
* @author rrqstart
* @Description 异步回调
* public class CompletableFuture implements Future, CompletionStage {//......}
*/
public class CompletableFutureTest {
public static void main(String[] args) throws Exception {
//异步调用,无返回值,开启一个新线程来执行任务
//public static CompletableFuture runAsync(Runnable runnable)
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":无返回值。");
});
Void v = completableFuture1.get();
System.out.println(v); //null
System.out.println("----------------------------------------");
//异步回调,有返回值,开启一个新线程来执行任务
//public static CompletableFuture supplyAsync(Supplier supplier)
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":有返回值。");
// int i = 10 / 0;
return 1024;
});
Integer i = completableFuture2.whenComplete((t, u) -> {
System.out.println("t = " + t);
System.out.println("u = " + u);
}).exceptionally(f -> {
System.out.println("exception : " + f.getMessage());
return 404;
}).get();
System.out.println(i);
}
}
//int i = 10 / 0;被注释掉时程序的输出结果:
ForkJoinPool.commonPool-worker-9:无返回值。
null
----------------------------------------
ForkJoinPool.commonPool-worker-9:有返回值。
t = 1024
u = null
1024
//int i = 10 / 0;没有被注释掉时程序的输出结果:
ForkJoinPool.commonPool-worker-9:无返回值。
null
----------------------------------------
ForkJoinPool.commonPool-worker-9:有返回值。
t = null
u = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
exception : java.lang.ArithmeticException: / by zero
404
package java.util.concurrent;
//Since:JDK1.5
public class Executors {
//1.创建一个只有一个线程的线程池:一个任务一个任务的执行,适用于需要保证顺序的执行各个任务。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
//2.创建一个固定线程数的可重用的线程池:执行长期任务性能好,适合严格限制线程数的场景,如:负载比较重的服务器。
public static ExecutorService newFixedThreadPool(int nThreads) {
//使用无界队列LinkedBlockingQueue(队列容量为:Integer.MAX_VALUE)作为线程池的工作队列
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
//3.创建一个线程数可伸缩的线程池:适用于执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重启它们。可扩容!
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
/**
* ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。
* 表示在给定的延迟之后执行任务,或者定期执行任务。
* ScheduledThreadPoolExecutor的功能与Timer类似,但其功能更强大、更灵活。
* Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以对应1个或多个后台线程。
*/
//4.创建固定数量线程的线程池:适合多个后台线程执行周期任务,同时为了满足资源管理的需求而限制后台线程数量的场景。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//5.创建单个线程的线程池:适合需要单个后台线程执行周期任务,同时需要保证顺序的执行各个任务的应用场景。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
}
在阿里巴巴《Java开发手册》中提到,使用 Executors 创建线程池可能会导致 OOM。比如:newFixedThreadPool
和newSingleThreadExecutor
中创建 LinkedBlockingQueue 时,默认并未指定容量,此时 LinkedBlockingQueue 就是一个无边界队列,最大长度为 Integer.MAX_VALUE,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。newCachedThreadPool
和newScheduledThreadPool
这两种方式创建的最大线程数可能是 Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致 OOM。避免使用 Executors 创建线程池,主要是避免使用其中的默认实现,我们可以自己直接调用 ThreadPoolExecutor 的构造函数来创建线程池。除了自己定义 ThreadPoolExecutor 外,还可以使用开源类库,如 apache、guava 等。
package java.util.concurrent; //since JDK1.5
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask类的部分源码(since JDK1.5)
package java.util.concurrent;
//RunnableFuture接口的父接口:Future、Runnable
//FutureTask类实现的接口:RunnableFuture、Future、Runnable
public class FutureTask<V> implements RunnableFuture<V> {
//构造器1
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; //ensure visibility of callable
}
//构造器2
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; //ensure visibility of callable
}
/**
* 根据run()方法被执行的时机,FutureTask可处于三种状态:
*(1)未启动:创建了FutureTask但还未执行run()方法时FutureTask处于该状态。
*(2)已启动:run()方法被执行时。
*(3)已完成:run()方法执行完后正常结束,或被cancel()方法取消,或run()方法抛出异常而异常结束时。
*/
public void run() { //...... }
/**
* 当FutureTask处于未启动或已启动状态时,执行get()方法将导致调用线程阻塞。
* 当FutureTask处于已完成状态时,执行get()方法将导致调用线程立即返回结果或抛出异常。
*/
public V get() throws InterruptedException, ExecutionException { //...... }
/**
* 当FutureTask处于未启动状态时,执行cancel(...)方法将导致此任务永远不会被执行。
* 当FutureTask处于已启动状态时,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务。
* 当FutureTask处于已启动状态时,执行cancel(false)方法将不会对正在执行此任务的线程产生影响。
* 当FutureTask处于已完成状态时,执行cancel(...)方法将返回false。
*/
public boolean cancel(boolean mayInterruptIfRunning) { //...... }
}
package com.java.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* @author rrqstart
* 创建线程的方式:
* * (1)继承Thread类
* * (2)实现Runnable接口:无泛型、重写run方法、无返回值、不可抛异常
* * (3)实现Callable接口:有泛型、重写call方法、有返回值、可抛异常
*
* FutureTask可用于异步获取执行结果或取消执行任务的场景。通过get()方法可以异步获取执行结果。
* 不论FutureTask调用多少次run()或者call()方法,它都能确保只执行一次Runable或Callable任务。
* 因此,FutureTask非常适合用于耗时高并发的计算,另外可以通过cancel()方法取消执行任务。
*/
public class ThreadWithCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(thread);
new Thread(futureTask, "A").start(); //在这个线程的任务耗时约2秒
//如果再添加一个线程,结果还是打印一条结果
new Thread(futureTask, "B").start();
//主线程可以完成自己的任务后,去获取其他线程的结果
System.out.println(Thread.currentThread().getName() + "-->计算完成");
//这个get方法,可能会产生阻塞,应该放在代码的最后,或者使用异步通信来处理
System.out.println(futureTask.get());
/*
* 执行结果:
* main-->计算完成
* ------ come in call------
* 1024
*/
}
}
//泛型带什么类型,call方法就返回什么类型
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("------ come in call------");
TimeUnit.SECONDS.sleep(2);
return 1024;
}
}
package java.util.concurrent;
//Since:JDK1.5
public class ThreadPoolExecutor extends AbstractExecutorService {
// 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;
//The context to be used when executing the finalizer, or null.
private final AccessControlContext acc;
private volatile int corePoolSize; //核心线程池的大小
private volatile int maximumPoolSize; //最大线程池的大小
private final BlockingQueue<Runnable> workQueue; //用来暂存任务的工作队列
private volatile long keepAliveTime;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
//默认的拒绝策略:AbortPolicy
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
//预定义的4种拒绝策略的静态内部类
public static class CallerRunsPolicy implements RejectedExecutionHandler {......}
public static class AbortPolicy implements RejectedExecutionHandler {......}
public static class DiscardPolicy implements RejectedExecutionHandler {......}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {......}
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
}
corePoolSize
:线程池中的常驻核心线程数,是线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池的基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()
方法,线程池会提前创建并启动所有基本线程。
- 核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干(铁饭碗),而非核心线程如果长时间的闲置,就会被销毁(临时工)。
maximumPoolSize
:线程池中能够容纳同时执行的最大线程数,也就是线程池允许创建的最大线程数,此值必须大于等于1。
- 该值等于核心线程数量 + 非核心线程数量。
keepAliveTime
:非核心线程闲置超时时长。当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余的线程会被销毁,直到只剩下corePoolSize个线程为止。
- 非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true),则也会作用于核心线程。
unit
:keepAliveTime的单位。workQueue
:任务队列,该阻塞队列中维护着被提交但等待执行的Runnable任务对象。threadFactory
:创建线程的工厂 ,用于批量创建线程,统一在创建线程时设置一些参数,如是否是守护线程、线程的优先级等。如果不指定,会新建一个默认的线程工厂。
- 通过Google工具包可以设置线程池里的线程名:
new ThreadFactoryBuilder().setNameFormat("general-detail-batch-%d").build()
handler
:拒绝策略。表示当队列和线程池都满了,即线程池处于饱和状态时,或者线程池已经关闭了的时候,如何来拒绝请求执行的任务的策略。
- AbortPolicy:默认的拒绝策略。直接抛出RejectedExecutionException异常阻止系统正常运行。
- CallerRunsPolicy:“调用者运行”的一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
- DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常,如果允许任务丢失,这是最好的一种策略。
public void execute(Runnable command)
:用于提交不需要返回值的任务,所以该方法无法判断任务是否被线程池执行成功。public Future> submit(Runnable task)
:用于提交需要返回值的任务,线程池会返回一个Future类型的对象,通过这个对象可以判断任务是否执行成功,并且可以通过这个对象的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完。public Future submit(Runnable task, T result)
public Future submit(Callable task)
public void shutdown()
:遍历线程池中的工作线程,然后逐个调用线程的interrupt()方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdown()方法只是将线程池的状态设置成SHUTDOWN,然后中断所有没有正在执行任务的线程。public List shutdownNow()
:遍历线程池中的工作线程,然后逐个调用线程的interrupt()方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow()方法首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。public boolean isShutdown()
:只要调用了shutdown()或shutdownNow()中的任意一个,isShutdown()方法就返回true。public boolean isTerminated()
:当所有任务都已关闭后,才表示线程池关闭成功,此时调用isTerminated()方法会返回true。maximumPoolSize = Ncpu + 1
(配置尽可能小的线程池)maximumPoolSize = Ncpu * Ucpu * (1 + W / C)
或maximumPoolSize = 2 * Ncpu
(由于IO密集型任务的线程并不是一直在执行任务,CPU的利用率不高,应配置尽可能大的线程池)
获取当前设备的逻辑处理器个数Ncpu:
int count = Runtime.getRuntime().availableProcessors();
线程池本身有一个调度线程,这个线程就是用于管理布控整个线程池里的各种任务和事务,例如创建线程、销毁线程、任务队列管理、线程队列管理等等,故线程池也有自己的状态。ThreadPoolExecutor 类中使用了一些 private static final int 型常量来表示线程池的状态。
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;
一个线程在创建的时候会指定一个线程任务,当执行完这个线程任务之后,线程自动销毁。但是线程池却可以复用线程,即一个线程执行完线程任务后不销毁,继续执行另外的线程任务。那么,线程池是如何做到线程复用的呢?ThreadPoolExecutor 在创建线程时,会将线程封装成工作线程 Worker,并放入工作线程组中,然后这个 Worker 反复从阻塞队列中拿任务去执行。
package com.java.threadpool;
import java.util.concurrent.*;
/**
* @author rrqstart
* Executors:3大方法
* ThreadPoolExecutor:7大参数
* RejectedExecutionHandler:4大拒绝策略
*/
public class ThreadPoolTest {
public static void main(String[] args) {
/*
* 线程池不允许使用 Executors 去创建!!!
* 而是通过 ThreadPoolExecutor 的方式创建线程池。这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。
* Executors 返回的线程池对象的弊端如下:
* (1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
* (2) CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
*/
//1.创建线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//new LinkedBlockingQueue() 等价于 new LinkedBlockingQueue(Integer.MAX_VALUE)
try {
for (int i = 1; i <= 10; i++) {
//2.向线程池提交任务
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " --> 办理业务!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//3. 关闭线程池
threadPool.shutdown();
}
}
}
//采取中止策略AbortPolicy的执行结果:
pool-1-thread-1 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-4 --> 办理业务!
pool-1-thread-3 --> 办理业务!
pool-1-thread-2 --> 办理业务!
pool-1-thread-4 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-1 --> 办理业务!
java.util.concurrent.RejectedExecutionException: Task com.java.threadpool.ThreadPoolTest$$Lambda$1/1023892928@4dd8dc3 rejected from java.util.concurrent.ThreadPoolExecutor@6d03e736[Running, pool size = 5, active threads = 4, queued tasks = 0, completed tasks = 4]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.java.threadpool.ThreadPoolTest.main(ThreadPoolTest.java:32)
Process finished with exit code 0
//采取调用者运行策略CallerRunsPolicy的执行结果:
pool-1-thread-1 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-1 --> 办理业务!
pool-1-thread-4 --> 办理业务!
pool-1-thread-3 --> 办理业务!
pool-1-thread-2 --> 办理业务!
main --> 办理业务!
pool-1-thread-1 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-1 --> 办理业务!
//采取丢弃策略DiscardPolicy的执行结果:
pool-1-thread-2 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-3 --> 办理业务!
pool-1-thread-3 --> 办理业务!
pool-1-thread-4 --> 办理业务!
pool-1-thread-1 --> 办理业务!
pool-1-thread-5 --> 办理业务!
pool-1-thread-2 --> 办理业务!
Tomcat 的整体架构包含连接器和容器两大部分,其中连接器负责与外部通信,容器负责内部逻辑处理。在连接器中:
Tomcat为了实现请求的快速响应,使用线程池来提高请求的处理能力。下面我们以HTTP非阻塞I/O为例对Tomcat线程池进行简要的分析。
在Tomcat中,通过AbstractEndpoint类提供底层的网络I/O的处理,若用户没有配置自定义公共线程池,则AbstractEndpoint通过createExecutor方法来创建Tomcat默认线程池。核心部分代码如下:
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
其中 TaskQueue、ThreadPoolExecutor 分别为 Tomcat 自定义任务队列、线程池实现。
Tomcat 自定义线程池继承于 java.util.concurrent.ThreadPoolExecutor,并新增了一些成员变量来更高效地统计已经提交但尚未完成的任务数量(submittedCount),包括已经在队列中的任务和已经交给工作线程但还未开始执行的任务。
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
// 新增的submittedCount成员变量,用于统计已提交但还未完成的任务数。
private final AtomicInteger submittedCount = new AtomicInteger(0);
private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);
// 构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
// 预启动所有核心线程
prestartAllCoreThreads();
}
}
Tomcat 在自定义线程池 ThreadPoolExecutor 中重写了 execute() 方法,并实现对提交执行的任务进行 submittedCount 加一。Tomcat 在自定义 ThreadPoolExecutor 中,当线程池抛出 RejectedExecutionException 异常后,会调用 force() 方法再次向 TaskQueue 中进行添加任务的尝试。如果添加失败,则 submittedCount 减一后,再抛出 RejectedExecutionException。
在 Tomcat 中重新定义了一个阻塞队列 TaskQueue,它继承于 LinkedBlockingQueue。在 Tomcat 中,核心线程数默认值为10,最大线程数默认为200,为了避免线程到达核心线程数后后续任务放入队列等待,Tomcat 通过自定义任务队列 TaskQueue 重写 offer() 方法实现了核心线程池数达到配置数后线程的创建。具体地,从线程池任务调度机制实现可知,当 offer() 方法返回 false 时,线程池将尝试创建新线程,从而实现任务的快速响应。TaskQueue 核心实现代码如下:
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent==null || parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
}
@Override
public boolean offer(Runnable o) {
// 1. parent为线程池,Tomcat中为自定义线程池实例
//we can't do any checks
if (parent==null) return super.offer(o);
// 2. 当线程数达到最大线程数时,新提交任务入队
//we are maxed out on threads, simply queue the object
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
// 3. 当提交的任务数小于线程池中已有的线程数时,即有空闲线程,任务入队即可
//we have idle threads, just add it to the queue
if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
// 4. 【关键点】如果当前线程数量未达到最大线程数,直接返回false,让线程池创建新线程
//if we have less threads than maximum force creation of a new thread
if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
// 5. 最后的兜底,放入队列
//if we reached here, we need to add it to the queue
return super.offer(o);
}
}
Tomcat 中通过自定义任务线程 TaskThread(继承自 Thread)实现对每个线程创建时间的记录;使用静态内部类 WrappingRunnable 对 Runnable 进行包装,用于对 StopPooledThreadException 异常类型的处理。
/**
* Allows the server developer to specify the acceptCount (backlog) that should be used for server sockets. By default, this value is 100.
*/
private int acceptCount = 100;
private int maxConnections = 10000;
使用局部线程池时,若任务执行完后没有执行 shutdown() 方法或有其他不当引用,极易造成系统资源耗尽。
为了更好地发现、分析和解决问题,建议在使用多线程时增加对异常的处理,异常处理通常有下述方案:
public void destroy() {
try {
poolExecutor.shutdown();
if (!poolExecutor.awaitTermination(AWAIT_TIMEOUT, TimeUnit.SECONDS)) {
poolExecutor.shutdownNow();
}
} catch (InterruptedException e) {
// 如果当前线程被中断,重新取消所有任务。
pool.shutdownNow();
// 保持中断状态
Thread.currentThread().interrupt();
}
}
为了实现优雅停机的目标,我们应当先调用 shutdown() 方法,调用这个方法也就意味着,这个线程池不会再接收任何新的任务,但是已经提交的任务还会继续执行。之后我们还应当调用 awaitTermination() 方法,这个方法可以设定线程池在关闭之前的最大超时时间,如果在超时时间结束之前线程池能够正常关闭则会返回 true,否则,超时会返回 false。通常我们需要根据业务场景预估一个合理的超时时间,然后调用该方法。
如果 awaitTermination() 方法返回 false,但又希望尽可能在线程池关闭之后再做其他资源回收工作,可以考虑再调用一下 shutdownNow() 方法,此时队列中所有尚未被处理的任务都会被丢弃,同时会设置线程池中每个线程的中断标志位。shutdownNow() 并不保证一定可以让正在运行的线程停止工作,除非提交给线程的任务能够正确响应中断。
// 在主线程中,开启鹰眼异步模式,并将ctx传递给多线程任务。
// 防止鹰眼链路丢失,需要传递。
RpcContext_inner ctx = EagleEye.getRpcContext();
// 开启异步模式
ctx.setAsyncMode(true);
//在线程池任务线程中,设置鹰眼rpc环境
private void runTask() {
try {
EagleEye.setRpcContext(ctx);
// do something...
} catch (Exception e) {
log.error("requestError, params: {}", this.params, e);
} finally {
// 判断当前任务是否是主线程在运行,当Rejected策略为CallerRunsPolicy的时候,核对当前线程
if (mainThread != Thread.currentThread()) {
EagleEye.clearRpcContext();
}
}
}