自定义线程池
class ThreadPoolTest{
public static void main(String[] args){
RejectPolicy<Runnable> rejectPolicy = BlockingQueue::put;
MyThreadPool pool = new MyThreadPool(3, 1000, TimeUnit.MILLISECONDS, 4, rejectPolicy);
for (int i = 0; i < 30; i++) {
int j = i;
pool.execuct(() -> {
log.info("i = {}", j);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue, T task);
}
public class MyThreadPool {
// 任务队列
private BlockingQueue<Runnable> blockingQueue;
// 工作队列
private final HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 等待任务的超时时间
private long timeout;
private TimeUnit timeUnit;
// 拒绝策略
private RejectPolicy<Runnable> rejectPolicy;
public MyThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.blockingQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
public void execuct(Runnable task){
// 当任务数没有超过coreSize时,可以直接交给worker对象执行
synchronized (workers) {
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.info("新增worker:{}",worker);
workers.add(worker);
worker.start();
} else {
// 当任务数超过coreSize时,加入任务队列暂存
blockingQueue.tryPut(task,rejectPolicy);
}
}
}
class Worker{
private Thread thread;
private Runnable task;
public Worker(Runnable task) {
this.task = task;
this.thread = new Thread(logicTask());
}
public void start(){
thread.start();
}
private Runnable logicTask(){
return () -> {
// task不为空,执行任务
// task为空,但是队列中有任务,将队列中的任务拿出来赋给task,在判断其是否为空
while (task != null || (task = blockingQueue.poll(timeout,timeUnit) )!=null){
try {
log.info("正在执行任务:{}",task);
task.run();
}catch (Exception e){
}finally {
task = null;
}
}
// 不再执行任务,需要将其移出workers
synchronized (workers){
workers.remove(this);
}
log.info("worker运行结束:{}",this);
};
}
}
}
class BlockingQueue<T>{
// 容量
private int capacity;
// 队列
private Deque<T> queue = new ArrayDeque<>();;
// 锁
private ReentrantLock lock = new ReentrantLock();
// 生产者信号量
private Condition fullCondition = lock.newCondition();
// 消费者信号量
private Condition emptyCondition = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
/**
* 无时间限制的等待
* @return
*/
public T poll(){
lock.lock();
try{
while (queue.isEmpty()){
try {
emptyCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T pop = queue.pop();
fullCondition.signal();
return pop;
}finally{
lock.unlock();
}
}
/**
* 有时间限制的等待
* @param time 等待时间
* @param unit 时间单位
* @return
*/
public T poll(long time, TimeUnit unit){
lock.lock();
try{
long nanos = unit.toNanos(time);
while (queue.isEmpty()){
try {
if (nanos <= 0){
return null;
}
nanos = emptyCondition.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T pop = queue.pop();
fullCondition.signal();
return pop;
}finally{
lock.unlock();
}
}
public void tryPut(T task, RejectPolicy<T> rejectPolicy){
lock.lock();
try{
// 队列是否已经满了
if (queue.size() == capacity){
rejectPolicy.reject(this,task);
}else {
// 队列有空位
log.info("加入任务队列:{}",task);
queue.addLast(task);
emptyCondition.signal();
}
}finally{
lock.unlock();
}
}
/**
* 放入任务
* @param task 待放入的任务
*/
public void put(T task){
lock.lock();
try{
while (queue.size() >= capacity){
try {
log.info("等待加入任务队列:{}",task);
fullCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("加入任务队列:{}",task);
queue.addLast(task);
emptyCondition.signal();
}finally{
lock.unlock();
}
}
public boolean put(T task, long timeout, TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while (queue.size() >= capacity){
try {
log.info("等待加入任务队列:{}",task);
if (nanos <= 0){
return false;
}
nanos = fullCondition.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("加入任务队列:{}",task);
queue.addLast(task);
emptyCondition.signal();
return true;
}finally{
lock.unlock();
}
}
public int size(){
lock.lock();
try{
return queue.size();
}finally{
lock.unlock();
}
}
}
JDK线程池
JDK线程池继承/实现关系
JDK线程池继承/实现关系如下图所示,其中:
- Executor : 执行器接口,可以用来执行提交的
Runnable
任务 - ExecutorService:执行服务器接口,可以用来调度执行已经提交一系列任务
- AbstractExecutorService:抽象执行服务器类,实现了
ExecutorService
中一些通用的功能,如非空判断等 - ThreadPoolExecutor:线程池执行器,JDK提供的线程池的经典实现
- ScheduledExecutorService:定时执行服务器接口,可以用来执行一些定时任务
- ScheduledThreadPoolExecutor:定时任务线程池执行器
ThreadPoolExecutor
的使用
线程池状态
状态名 | 高 3位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入终结 |
TERMINATED | 011 | - | - | 终结状态 |
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量 ctl
中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 CAS 原子操作进行赋值。
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
构造方法
ThreadPoolExecutor
的完整构造方法如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数目 (最多保留的线程数)maximumPoolSize
最大线程数目keepAliveTime
生存时间 - 针对救急线程unit
时间单位 - 针对救急线程workQueue
阻塞队列threadFactory
线程工厂 - 可以为线程创建时起个好名字handler
拒绝策略
核心线程与救急线程
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
- 当线程数达到
corePoolSize
并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue
队列排队,直到有空闲的线程。 - 如果队列选择了有界队列,那么任务超过了队列大小时,会创建
maximumPoolSize - corePoolSize
数目的线程来救急。
拒绝策略
如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现:
AbortPolicy
让调用者抛出RejectedExecutionException
异常,这是默认策略CallerRunsPolicy
让调用者运行任务DiscardPolicy
放弃本次任务DiscardOldestPolicy
放弃队列中最早的任务,本任务取而代之- Dubbo 的实现,扩展了
AbortPolicy
,在抛出RejectedExecutionException
异常之前会记录日志,并 dump 线程栈信息,方便定位问题 - Netty 的实现,是创建一个新线程来执行任务
- ActiveMQ 的实现,带超时等待(60s)尝试放入队列
- PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime
和unit
来控制。
提交任务
线程池除了能够执行Runnable
这种无返回值的任务外,可以执行其它Callable
任务
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
关闭线程池
关闭线程池存在两种方法,一种时比较温和的shutdown()
方法,另一种时比较粗暴的shutdownNow()
方法。
shutdown()
执行shutdown()
方法会使线程池进入SHUTDOWN状态:
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
其源码如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
shutdownNow()
执行shutdownNow()
方法会使线程池进入STOP状态:
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
其源码如下:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
其它常用方法
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
生成ThreadPoolExecutor
对象
通常情况下,我们不会直接调用ThreadPoolExecutor
的构造方法,而是使用JDKExecutors
中提供了众多工厂方法来创建各种用途的线程池。
newFixedThreadPool
其方法如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:
- 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
- 阻塞队列采用了
LinkedBlockingQueue
,它是无界的,可以放任意数量的任务 - 适用于任务数量已知,相对耗时的任务
如果需要自定义线程名,可以采用方法newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
并传入线程工厂。
newCachedThreadPool
其方法如下
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:
- 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着:
- 全部都是救急线程(60s 后可以回收)
- 救急线程可以无限创建
- 队列采用了
SynchronousQueue
实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货) - 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。
newSingleThreadExecutor
其方法如下
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用场景:
- 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
与单独开一个线程执行的区别:
- 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
与newFixedThreadPool(1)
的区别:
- Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
- FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
- Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
- 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
ScheduledThreadPoolExecutor
的使用
线程池ScheduledThreadPoolExecutor
是JDK1.5引入的,在此之前,执行定时任务或延时任务都是使用的Timer
。但是Timer
具备以下缺点:
- 所有任务都是由同一个线程来调度,因此所有任务都是串行执行的
- 同一时间只能有一个任务在执行
- 前一个任务的延迟或异常都将会影响到之后的任务
延时执行
在ScheduledThreadPoolExecutor
中,执行延时任务可以采用schedule()
方法,其中command
或者callable
是需要执行的任务,delay
是延时时间,unit
是时间单位。
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit)
周期执行
定时执行任务或周期执行任务可以使用scheduleAtFixedRate()
和scheduleWithFixedDelay()
两个方法。这两个方法拥有相似的函数签名,但是他们了对周期的控制并不相同。
其中scheduleAtFixedRate()
的函数签名如下:其中控制周期的参数为period
。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)
在scheduleAtFixedRate()
中,任务会依次执行,不会重叠执行,如果其中一次花费的时间较长,导致后面的任务无法按时执行,那么下一次任务会立即启动,直至追上预设的任务周期。
以下面的测试代码为例,task
中每打印一次cnt
就是使当前线程进入睡眠状态,初始睡眠时间为4秒,并逐次减半。另一方面,我们设置task
的执行周期为2秒每次,其周期小于初始睡眠时间。
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程-" + (i++));
}
};
ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1,factory);
AtomicLong sleepTime = new AtomicLong(4000);
AtomicInteger cnt = new AtomicInteger(0);
log.info("start...");
Runnable task = () -> {
log.info("cnt = {}", cnt.incrementAndGet());
try {
Thread.sleep(sleepTime.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepTime.updateAndGet(v -> v / 2);
log.info("finish...");
};
pool.scheduleAtFixedRate(task,1,2, TimeUnit.SECONDS);
}
代码打印日志如下,我们可以对其进行进一步分析。
[190 ms] [INFO][main] i.k.e.c.e.t.ScheduledThreadPoolDemo : start...
[1233 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 1
[5245 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[5245 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 2
[7254 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[7254 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 3
[8260 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[8260 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 4
[8766 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[9229 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 5
[9481 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[11237 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 6
[11363 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
每次任务的启动时间如下边所示,分析得出,整个周期任务在第4次追上了预设时间,第5次往后开始按照预设周期执行。
任务 | 预计启动时间 | 实际启动时间 | 实际结束时间 | 备注 |
---|---|---|---|---|
1 | 1190 | 1233 | 5245 | 睡眠了4秒,导致后面的任务无法按照预计时间执行 |
2 | 3190 | 5245 | 7254 | 上一次结束时间超过本次的预计启动时间,需要立即执行 |
3 | 5190 | 7254 | 8260 | 同上 |
4 | 7190 | 8260 | 8766 | 同上 |
5 | 9190 | 9229 | 9481 | 上一次结束时间小于本次的预计启动时间,按计划执行即可 |
6 | 11190 | 11237 | 11363 | 同上 |
而方法scheduleWithFixedDelay()
与此不同,他会在执行完才开始等待,确保每两次任务之间拥有相同的空闲时间。其方法签名如下,其中delay
用来控制空闲窗口的大小。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit)
定时任务
如何让每周五中午12点执行任务?这本质上还是一个周期任务,但是需要计算以下两个数据:
initialDelay
:当前时间距离下一个周五中午12点还有多久?period
:执行的周期是多久?
由于两者共用了同一个时间unit
,所以initialDelay
与period
需要换算为一致的单位,采用毫秒最为合适。
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 设置到本周周五
LocalDateTime target = now.withHour(12).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.FRIDAY);
// 如果目标时间比当前时间小,则需要在目标时间上加1周
if (now.isAfter(target)){
target = target.plusWeeks(1);
}
// 计算两者的差值就是初始延时时间,转化为毫秒
long initialDelay = Duration.between(now, target).toMillis();
// 计算周期:可以使用时间工具类直接将天数转化为毫秒
long period = TimeUnit.DAYS.toMillis(7);
如何正确处理线程池中的异常
任务自身处理异常
在任务最外围使用try-catch
代码块,捕获任务中可能出现的,然后将其记录到日志中。
借助Future
对象
在下面的代码中,我们向线程池提交了任务task
,并拿到了相应的Future
对象,在一般情况下,线程正常结束,future.get()
会得到返回值,但如果task
在执行期间出现异常,future.get()
则会抛出ExecutionException
异常,我们可以通过捕获该异常来进一步得到线程中实际发生的异常。
public static void main(String[] args) throws ExecutionException, InterruptedException {
int nThreads = 5;
ThreadFactory factory = new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程-" + (i++));
}
};
ExecutorService executor = new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
factory);
Callable<Boolean> task = () -> {
log.info(Thread.currentThread().getName());
return true;
};
Future<Boolean> future = executor.submit(task);
log.info("ret : {}",future.get());
}
工作线程
定义
让有限的工作线程来轮流异步处理无限多的任务。它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
注意:不同任务类型应该使用不同的线程池,这样能够避免饥饿,并提升效率。
饥饿
现象
固定大小的线程池会有饥饿现象:
- 两个工人是同一个线程池中的两个线程
- 它们要做的事:给客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点餐,等餐做好,才能上菜,在此期间工人必须等待
- 后厨做菜:只需要做菜即可
- 当来了一个客人时,工人A去点餐,工人B去做菜
- 当来了两个客人时,他们都去点餐了,没人做菜,造成饥饿
造成该现象的原因:固定大小的线程池会数显线程数量不足的情况
解决
方案1:增大线程池大小,但并非根本解决之道,因为如果之后工作密度增大,还是会出现饥饿现象。
方案2:不同任务类型应该使用不同的线程池。
线程池大小的设定
线程池过小:线程池吞吐量过小,处理速度较慢。
线程池过大:频繁进行上下文切换会产生大量额外的开销,同时过多的线程会占用大量的内存。
CPU密集型运算
应用程序主要用来进行计算和数据分析,此时最佳值为: CPU核心数 + 1。
此外,+ 1 是为了保障如果当前线程因为操作系统的故障导致线程暂停,额外的线程能够顶上去,保证CPU时钟周期不被浪费。
I/O密集型运算
I/O密集型的运算中,CPU不总是处于繁忙状态,例如,当你进行IO操作,包括进行数据操作和RPC的调用时,CPU就闲下来了,因此可以利用多线程提高它的利用率。
经验公式如下:
线程数 = 核数 * 期望 CPU利用率 * (CPU计算时间 + CPU等待时间)/ CPU计算时间
时间占比如何估算:运行程序,利用分析工具进行统计,不需要太精确
Tomcat中的线程池
Tomcat有两大核心组件:连接器Connector和容器Container。Tomcat在Connector使用了线程池,具体示意图如下:
- LimitLatch 用来限流,可以控制最大连接的个数,类似JUC中的
Semaphore
- Acceptor 只负责接收新的Socket连接
- Poller 只负责监听socket channel中是否有可读的I/O事件
- 一旦可读,Poller 会封装一个任务对象sockerProcessor,提交给Executor线程池处理
- Executor线程池中的工作线程负责最终的请求处理
可见:合理的分工是实现高性能的保障
Tomcat中线程池的实现
Tomcat中线程池扩展了ThreadPoolExecutor
,其行为稍有不同,主要体现在拒绝策略上
如果线程池中的线程数达到了maximumPoolSize
,它不会立刻抛出异常,而是会再次尝试将任务放入队列,如果仍然失败,才会抛出RejectedExecutionException
。
Tomcat线程池的配置
Connector配置
对应文件server.xml中Connector标签。
配置项 | 默认项 | 说明 |
---|---|---|
acceptorThreadCount | 1 | Acceptor线程数量,没有必要不需要调大,因为大部分情况下处于阻塞状态,因为没有新的连接 |
pollerThreadCount | 1 | Poller线程数量,没有必要不需要调大,Poller采用NIO多路检测与复用的思想 |
minSpareThreads | 10 | 核心线程数 |
maxThreads | 200 | 最大线程数 |
executor | - | Executor名称,用来引用对应的Executor配置,如果定义了,则会覆盖上面的minSpareThreads和maxThreads |
Executor配置
对应文件server.xml中Executor标签,默认是被注释掉的。
配置项 | 默认项 | 说明 |
---|---|---|
threadPriority | 5 | 线程优先级 |
daemon | true | 是否为守护线程 |
minSpareThreads | 25 | 核心线程数 |
maxThreads | 200 | 最大线程数 |
maxIdleTime | 60000 | 救急线程的生存时间,单位为毫秒,即默认值为1分钟 |
maxQueueSize | Integer.MAX_VALUE | 阻塞队列长度,如果将来服务器压力很大,可能造成任务的堆积,此时应该调整为一个合适的值 |
prestartminSpareThreads | false | 核心线程是否在服务器启动时初始化 |
Fork/Join线程池
概念
Fork/Join线程池是在JDK1.7加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的CPU密集型运算。
所谓任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解,跟递归相关的一些计算,如归并排序、斐波那契数列,都可以使用分治思想求解。
Fork/Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升运算效率。
Fork/Join默认会创建于CPU核心数大小相同的线程池。
使用
提交给 Fork/Join 线程池的任务需要继承 RecursiveTask
(有返回值)或 RecursiveAction
(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务。
class SumTask extends RecursiveTask<Integer> {
int left;
int right;
public SumTask(int left, int right) {
this.left = left;
this.right = right;
}
@Override
protected Integer compute() {
// 如果 left == right,可以求得结果了
if (left == right) {
log.debug("join() {}", left);
return left;
}
int mid = left + (right - left) / 2;
// 将任务进行拆分(fork)
SumTask t1 = new SumTask(left, mid);
SumTask t2 = new SumTask(mid + 1, right);
t1.fork();
t2.fork();
log.debug("fork() {} + {}", t1, t2);
// 合并(join)结果
int result = t1.join() + t2.join();
log.debug("join() {} + {} = {}", t1, t2, result);
return result;
}
@Override
public String toString() {
return String.format("{%d, %d}", left, right);
}
}
对任务定义完成后,可以向ForkJoinPool
对象提交任务, 并得到返回值。使用方法如下:
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Integer ret = pool.invoke(new SumTask(1,6));
System.out.println("ret = " + ret);
}
打印日志如下
[179 ms] [DEBUG][ForkJoinPool-1-worker-3] i.k.e.c.e.t.SumTask : fork() {4, 5} + {6, 6}
[180 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : fork() {0, 1} + {2, 3}
[185 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() 0
[179 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : fork() {4, 4} + {5, 5}
[185 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() 1
[179 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : fork() {2, 2} + {3, 3}
[180 ms] [DEBUG][ForkJoinPool-1-worker-4] i.k.e.c.e.t.SumTask : fork() {0, 0} + {1, 1}
[179 ms] [DEBUG][ForkJoinPool-1-worker-1] i.k.e.c.e.t.SumTask : fork() {0, 3} + {4, 6}
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() 4
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() 5
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 2
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 3
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() {2, 2} + {3, 3} = 5
[186 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 6
[185 ms] [DEBUG][ForkJoinPool-1-worker-4] i.k.e.c.e.t.SumTask : join() {0, 0} + {1, 1} = 1
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() {4, 4} + {5, 5} = 9
[186 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() {0, 1} + {2, 3} = 6
[186 ms] [DEBUG][ForkJoinPool-1-worker-3] i.k.e.c.e.t.SumTask : join() {4, 5} + {6, 6} = 15
[186 ms] [DEBUG][ForkJoinPool-1-worker-1] i.k.e.c.e.t.SumTask : join() {0, 3} + {4, 6} = 21
ret = 21.0
JDK1.8改进
在JDK1.8中,Stream
被引入,它可以用来自动拆分工作而不需要我们手动拆分,简化了对任务拆分的要求。