目录
2. Executor与ExecutorService源码对比
4. ScheduledThreadPoolExecutor详解
一、线程池
线程池目的:通过减少频繁创建和销毁线程来降低性能损耗。每个线程都需要一个内存栈,用于存储局部变量、操作栈等信息,可以通过-Xss参数来调整每个线程栈大小(默认64位OS为1024KB)。
线程池一般配合队列一起工作。设置队列的大小,当超出该阈值后,就进入拒绝策略来处理,从而保护系统免受大流量而导致崩溃。
创建多少线程?要根据实际业务情况来压测决定。任务类型IO密集型还是CPU密集型、CPU核数,来设置合理的线程池大小、队列大小、拒绝策略等参数。maximumPoolSize(最大核心线程数)设置过大,导致瞬间线程数很多,同时使用LinkedBlockingQueue有大量任务等待线程执行,则会出现GC慢等问题,造成系统响应慢甚至OOM。
注意线程池执行中无法捕获堆栈上下文,需要记录相关参数,以方便定位问题。
1. 两级调度模型
Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,操作系统线程也会被回收。
如下图所示是Executor框架的两级调度模型。Executor调度任务给线程池里的线程;操作系统调度CPU执行线程。注意:操作系统调度不受Executor调度的影响。
2. Executor执行过程
如下图所示是ThreadPoolExecutor执行示意图。步骤如下,参考上图:
- 1)当前运行的线程数 < corePoolSize,则创建新线程来执行任务(需要获取全局锁);
- 2)当前运行的线程数 >= corePoolSize,则任务加入阻塞BlockingQueue;
- 3)阻塞队列BlockingQueue已满,则创建新的线程来处理任务(需要获取全局锁);
- 4)如果创建新线程将使当前运行的线程 > maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
注意:尽量避免创建新线程,原因是获取全局锁,但是大部分任务加入阻塞队列中;创建的线程会封装成java.util.concurrent.ThreadPoolExecutor.Worker。
3. Executor结构组成
如下图所示是Executor的使用。可以看出3大部分组成:
- 定义任务:实现Runnable(无返回)接口或Callable接口(有返回);
- 执行任务:线程执行器(ThreadPoolExecutor、ScheduledThreadPoolExecutor);
- 任务结果:接口Future和实现Future接口的FutureTask类。
4. 线程池参数配置
参数 | 说明 |
corePoolSize | 核心线程数:即使线程空闲也不会释放(最小线程数) |
maximumPoolSize | 最大线程数:线程池最多维护的线程数 |
keepAliveTime | 线程空闲时间:超出该时间,线程被销毁回收,直至corePoolSize |
threadFactory | 线程工厂:创建线程,设置线程名、是否是后台线程 |
workQueue | 任务队列: ArrayBlockingQueue:有界阻塞数组队列 LinkedBlockingQueue:有界/无界阻塞链表队列 PriorityBlockingQueue:优先级阻塞队列 SynchronousQueue:无缓冲阻塞队列 |
rejectedExecutionHandler | 缓冲队列满后的拒绝策略,见上图 |
注意:a. 有界阻塞队列需要合理设置队列大小;
b. SynchronousQueue:当插入一个任务时,必须要移除另一个任务;移除任务时,则必须插入另一个任务。(没有缓冲)
二、代码示例
1. 定义线程池
类型 | 特点 |
Executors.newSingleThreadExecutor() | 1. 单线程线程池; 2. 线程空闲时间是0L; 3. 阻塞队列是LinkedBlockingQueue。 |
Executors.newScheduledThreadPool(corePoolSize) | 1. 固定数量线程池; 2. 线程空闲时间是0L; 3. 阻塞队列是LinkedBlockingQueue。 |
Executors.newCachedThreadPool() | 1. 可缓存线程池(按任务需要创建线程); 2. 线程空闲时间是60L; 3. 阻塞队列是SynchronousQueue。 |
Executors.newScheduledThreadPool(corePoolSize) | 1. 固定数量的延时或周期执行线程池; 2. 线程空闲时间是0L; 3. 阻塞队列是DelayedWorkQueue。 |
Executors.newWorkStealingPool(parallelism) | 1. Fork/Join线程池(ForkJoinPool); 2. 工作窃取work-stealing算法; 3. FIFO的双端队列ForkJoinPool.WorkQueue。 |
new ThreadPoolExecutor() | 自定义线程池 |
package com.common.instance.test.config.pool;
import com.log.util.LogUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
/**
* @description 线程池
* @author tcm
* @version 1.0.0
* @date 2021/12/2 9:53
**/
@Component
public class ExecutorHelper {
// 单线程线程池
@Bean("mySingleExecutor")
public ExecutorService singleExecutor() {
/*
等价于:
ExecutorService executorService = new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
*/
return Executors.newSingleThreadExecutor();
}
// 固定数量线程池
@Bean("myFixedExecutor")
public ExecutorService fixedExecutor() {
/*
等价于:
ExecutorService executorService = new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
*/
return Executors.newFixedThreadPool(10);
}
// 可缓存线程池
@Bean("myCachedExecutor")
public ExecutorService cachedExecutor() {
/*
等价于:
ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
*/
return Executors.newCachedThreadPool();
}
// 延迟执行线程池
@Bean("myScheduledExecutor")
public ExecutorService scheduledExecutor() {
/*
等价于:
ScheduledExecutorService scheduledExecutorService = new ScheduledExecutorService(
corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
*/
return Executors.newScheduledThreadPool(10);
}
// work-stealing线程池
@Bean("myWorkStealingExecutor")
public ExecutorService workStealingExecutor() {
/*
等价于:
ExecutorService executorService = new ForkJoinPool(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
默认并行数:Runtime.getRuntime().availableProcessors():物理机核数
*/
return Executors.newWorkStealingPool(10);
}
// 自定义线程池
@Bean("myPoolExecutor")
public ExecutorService myPoolExecutor() {
// 定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
// 核心线程数
10,
// 最大线程数
40,
// 线程空闲时间
60,
// 空闲时间单位
TimeUnit.SECONDS,
// 队列大小
new LinkedBlockingQueue<Runnable>(200),
// 线程工厂创建线程
new MyThreadFactory("myPoolExecutor-"),
// 线程决绝策略
new ThreadPoolExecutor.CallerRunsPolicy());
// JVM关闭时,释放线程资源
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
// 关闭线程池,不接受新任务
executor.shutdown();
// 等待多久剩余的线程终止操作
executor.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LogUtil.error("ExecutorHelper.myPoolExecutor()", "myPoolExecutor awaitTermination failed ", e);
}
}));
return executor;
}
}
2. 自定义线程工厂
package com.common.instance.test.config.pool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description 定义线程工厂 - 参考DefaultThreadFactory
* @author tcm
* @version 1.0.0
* @date 2021/12/2 16:45
**/
public class MyThreadFactory implements ThreadFactory {
// 池大小
private final AtomicInteger poolNumber = new AtomicInteger(1);
// 线程组
private final ThreadGroup threadGroup;
// 线程编号
private final AtomicInteger threadNumber = new AtomicInteger(1);
// 线程前缀
public final String namePrefix;
public MyThreadFactory(String name){
SecurityManager s = System.getSecurityManager();
threadGroup = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
if (null==name || "".equals(name.trim())){
name = "pool";
}
namePrefix = name +"-"+
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(threadGroup, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
3. 调用
package com.common.instance.test.controller;
import com.common.instance.test.core.Response;
import com.common.instance.test.entity.WcPendantTab;
import com.common.instance.test.service.WcPendantTabService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* Tab菜单(WcPendantTab)表控制层
*
* @author tcm
* @since 2021-01-14 15:02:08
*/
@Slf4j
@RestController
@RequestMapping("/tab")
@Api(tags = "活动tab测试")
public class WcPendantTabController {
@Resource
private WcPendantTabService wcPendantTabService;
@Resource(name = "myPoolExecutor")
private ExecutorService executor;
@PostMapping("/testPoolExecutor")
@ApiOperation("测试线程池")
public Response<List<WcPendantTab>> testPoolExecutor(WcPendantTab tab){
Response<List<WcPendantTab>> response = new Response<>();
try {
// 任务提交到线程池
Future<Object> future = executor.submit(() -> wcPendantTabService.queryAll(tab));
// 获取任务执行结果
List<WcPendantTab> result = (List<WcPendantTab>) future.get();
response.setData(result);
} catch (Exception e) {
e.printStackTrace();
return Response.error();
}
return response;
}
}
4. 其他测试
package com.cmmon.instance;
import org.junit.jupiter.api.Test;
import java.util.Objects;
import java.util.concurrent.*;
/**
* @description 线程池测试
* @author TCM
* @version 1.0
* @date 2022/4/17 17:32
**/
public class ExecutorTest {
ThreadPoolExecutor cachedThreadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
@Test
public void cachedThreadPoolTest() throws InterruptedException {
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread() + ", cachedThreadPool.getPoolSize(): " + cachedThreadPool.getPoolSize());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
Thread.sleep(6000);
System.out.println("cachedThreadPool工作队列类型: " + cachedThreadPool.getQueue().getClass());
System.out.println("6秒后的池线程数量: " + cachedThreadPool.getPoolSize());
Thread.sleep(62000);
System.out.println("62秒后的池线程数量: " + cachedThreadPool.getPoolSize());
}
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
@Test
public void scheduledThreadPoolTest() throws InterruptedException {
// 延迟任务执行
scheduledThreadPool.schedule(() -> {
System.out.println("延时执行任务");
}, 10, TimeUnit.SECONDS);
// 延时后固定周期执行
// scheduleAtFixedRate(): 延时后以固定周期执行
// scheduleWithFixedDelay(): 延时后以固定的延迟来执行(前次任务执行完毕后再延时period)
scheduledThreadPool.scheduleAtFixedRate(() -> {
System.out.println("固定周期执行任务");
}, 2, 1, TimeUnit.SECONDS);
Thread.sleep(40000);
System.out.println("main thread end");
}
final ConcurrentMap<String, Future<String>> concurrentMap = new ConcurrentHashMap<>();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
// 测试FutureTask
@Test
public void futureTaskTest() throws InterruptedException {
for (int i = 0; i < 10; i++) {
String taskName = executionTask("taskName" + i);
System.out.println(taskName);
}
}
private String executionTask(final String taskName) {
while (true) {
Future<String> future = concurrentMap.get(taskName);
if (Objects.isNull(future)) {
// 定义任务
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return taskName;
}
};
// 定义一个FutureTask任务
FutureTask<String> futureTask = new FutureTask<>(callable);
future = concurrentMap.putIfAbsent(taskName, futureTask);
if (Objects.isNull(future)) {
future = futureTask;
// FutureTask直接执行
futureTask.run();
// 提交给线程池执行
// fixedThreadPool.submit(futureTask);
}
}
try {
return future.get();
} catch (Exception e) {
e.printStackTrace();
concurrentMap.remove(taskName, future);
}
}
}
}
三、源码解析
1. 类图
JDK中ExecutorService有三种实现:
ThreadPoolExecutor:标准线程池
ScheduledThreadPoolExecutor:延迟任务线程池
ForkJoinPool:work-steeling模式线程池。池中的每个线程都创建一个队列,从而使用work-steeling算法(任务窃取)使得任务处理完成的线程从其他忙的线程队列中窃取任务。
2. Executor与ExecutorService源码对比
package java.util.concurrent;
/**
* 1.该接口提交任务,使得提交任务与任务处理隔离,不显示创建线程;
* 2.接受一个Runnable接口对象,并没有返回结果
*/
public interface Executor {
/**
* 提交任务,使得线程池中的线程处理任务逻辑
* 注意:该方法并没有返回任务的处理结果
* @param command Runnable任务
* @throws RejectedExecutionException 线程池拒绝异常
* @throws NullPointerException 空指针异常
*/
void execute(Runnable command);
}
package java.util.concurrent;
import java.util.List;
import java.util.Collection;
/**
* 1. 提供管理终止的方法,这些方法可跟踪异步线程执行任务的进度
* 2. 关闭线程池有两种方法:shutdown()、shutdownNow()
*/
public interface ExecutorService extends Executor {
/**
* 关闭线程池
* 1. 拒绝新的任务提交
* 2. 正在执行的任务完成后关闭
*/
void shutdown();
/**
* 立刻关闭线程池
* 1. 拒绝新的任务提交
* 2. 终止等待执行的任务,返回任务
* 3. 终止正在执行的任务,返回任务
*/
List<Runnable> shutdownNow();
// 判定线程池是否关闭
boolean isShutdown();
// shutdown()后所有任务是否终止
boolean isTerminated();
// shutdown()后所有任务在timeout后终止
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交任务并返回任务处理结果
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
Executor | ExecutorService |
1.execute()提交任务; 2.接受Runnable接口对象; 3.无返回任务处理结果 | 1.submit()提交任务; 2.接受Runnable、Callable接口对象; 3.返回任务处理结果; 4.继承Executor接口; 5.有控制线程池的方法,如shutdown() |
3. CachedThreadPool详解
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
如上代码所示,CachedThreadPool是按需创建新线程的线程池,即:corePoolSize=0。keepAliveTime=60L,空闲线程超过60秒后将会被终止。阻塞队列SynchronousQueue,它是一个没有容量的阻塞队列,即:每个插入操作必须等待另一个线程的对应移除操作,反之亦然。
没有容量的SynchronousQueue作为线程池的工作队列,又是无界的。这意味着,如果主线程提交任务的速度高于线程池线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
如上图所示,对步骤进行说明如下:
- step1:执行offer()时,如果池中有空闲线程正在执行poll(),那么主线程执行offer操作与空闲线程执行poll操作配对成功,主线程把任务交给空闲线程执行;否则执行step2。
- step2:池为空或当前没有空闲线程时,此时会创建一个新线程执行任务;
- step3:step2创建新线程完成任务后,主线程60秒钟内提交了一个新任务,那么执行step1;否则,将终止空闲线程。
4. ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,用来延迟或定期执行任务。它比Timer更强大。Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
以上代码所示, keepAliveTime=0L,空闲线程会立即被终止。使用DelayedWorkQueue阻塞队列存储任务java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask,具有延时和周期执行任务。
ScheduledFutureTask类主要有三个变量,如下。注意time相同时,按sequenceNumber小的优先出队。
- sequenceNumber:添加到阻塞队列的序列号
- time:任务执行的绝对时间
- period:执行任务的间隔周期
如下步骤是ScheduledFutureTask的任务执行步骤:
- 1)线程获取可以出队的任务(ScheduledFutureTask的time > 当前时间,说明当前任务可以出队);
- 2)线程执行出队任务;
- 3)线程修改出队任务的time变量(下次被执行的时间);
- 4)完成任务后,放回队列。
5. Future源码
package java.util.concurrent;
/**
* 返回异步计算的结果
* 检查计算是否完成;等待计算完成;结算完成获取结果
*/
public interface Future<V> {
// 取消任务的执行
boolean cancel(boolean mayInterruptIfRunning);
// 任务完成前,将其取消,返回true
boolean isCancelled();
// 任务正常完成,返回true
boolean isDone();
// 等待任务执行完成,获取执行结果
V get() throws InterruptedException, ExecutionException;
// 等待多久后,获取执行结果(正常完成),否则抛出异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
6. FutureTask详解
FutureTask实现Future接口,还实现了Runnable接口。因此,FutureTask任务给Executor执行,也可以线程直接执行(FutureTask.run())。
上图所示是FutureTask的状态变化示意图,有下面三种状态:
- 未启动:创建任务,但是调用run()之前;
- 已启动:调用run()方法;
- 已完成:任务正常结束、被取消cancel()、抛出异常。
上图所示,注意:任务未启动时,调用cancel()导致此任务永远不会被执行;任务已完成时,调用cancel()方法将返回false。
四、参考资料
Springboot学习笔记(一)-线程池的简化及使用 - 舒山 - 博客园