Class简介
功能简介
Java默认线程池java.util.concurrent.ThreadPoolExecutor,核心线程开启过多,会导致线程栈常驻内存 (即使大部分时间空闲),若每个线程栈1MB,100个核心线程就会占用100MB堆外内存,还可能不够用。
举例,线程池配置:核心线程100,最大线程150,有界队列容量1000。
当某一时段遇到大量任务涌入时(200个慢速job统计任务 + 10个快速消息通知 + 断续提交的任务)
- 超过核心线程数的任务,会被缓存到队列中,等待被核心线程执行(这时积压的任务并不会创建新的非核心线程去执行);
- 除非任务提交到最大队列容量1000,才会触发拒绝策略,从而创建非核心线程执行任务。但大部分场景的队列容量大,很难达到满载容量;
- 后续继续提交到此线程池的快速任务,也会因为核心线程被全部阻塞、队列未满载,而无法及时得到处理;
因此,采用 临时工 策略,能大幅降低资源,同时也可将核心线程数设置为绝大部分情况下的够用数值,但也会引起JVM堆外内存波动(此工具适用大部分场景,也要酌情考虑使用)。
通过入队时java.util.Queue.boolean offer(E e),触发拒绝策略(返回false)伪造队列已满载,欺骗线程池创建新的非核心线程(临时工)去执行积压任务,消耗完成后,非核心线程会超时自行销毁。
除非慢任务积压到最大线程数都处理不完,才会导致后续任务不得不进入队列等待。
环境
JDK-8
若需使用Spring线程池,需引入第三方Jar包:org.springframework.spring-context
Maven
pom.xml
xml
<dependency><!-- 按需引入 -->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
Class
Threading.java
java
package com.qoo.util;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 线程工具类
* @author Haining.Liu
* @date 2022-09-30 10:05:05
*/
public class Threading {
/**
* 系统CPU线程数
*/
public static final int THREADS = Runtime.getRuntime().availableProcessors();
public static final String POOL_NAME = "threadPool";
public static final ThreadPoolTaskExecutor POOL = pool(
THREADS >> 2,
THREADS * 12,
Short.MAX_VALUE,
Duration.ofSeconds(10),
"G-"
);
public static final ThreadPoolExecutor POOL0 = pool0(
THREADS >> 2,
THREADS * 12,
Short.MAX_VALUE,
Duration.ofSeconds(10),
"G0-"
);
/**
* 创建线程池
* @param core {@code int}:核心线程数;
* @param max {@code int}:最大线程数;
* @param capacity {@code int}:队列容量;
* @param keepAlive {@link Duration}:线程空闲时间;
* @param preName {@link String}:线程池名称前缀;
* @return {@link ThreadPoolExecutor}
*/
public static ThreadPoolExecutor pool0(int core, int max, int capacity, Duration keepAlive, String preName) {
SpeedQueue<Runnable> queue = new SpeedQueue<>(capacity);
TryPolicy policy = new TryPolicy();
ThreadPoolExecutor exe = new ThreadPoolExecutor(
core = Math.max(core, 2),
Math.max(max, core),
Math.max(keepAlive.toMillis(), 2000), TimeUnit.MILLISECONDS,
queue,
policy
);
queue.setPool(exe);
policy.setPool(exe);
return exe;
}
/**
* 创建Spring线程池
* @param core {@code int}:核心线程数;
* @param max {@code int}:最大线程数;
* @param capacity {@code int}:队列容量;
* @param keepAlive {@link Duration}:线程空闲时间;
* @param preName {@link String}:线程池名称前缀;
* @return {@link ThreadPoolTaskExecutor}
*/
public static ThreadPoolTaskExecutor pool(int core, int max, int capacity, Duration keepAlive, String preName) {
SpeedQueue<Runnable> queue = new SpeedQueue<>(capacity);
TryPolicy policy = new TryPolicy();
ThreadPoolTaskExecutor exe = new ThreadPoolTaskExecutor() {
private static final long serialVersionUID = -7850550293291372403L;
@Override
protected BlockingQueue<Runnable> createQueue(int capacity) {
if (capacity > 0) {
return queue;
} else {
return super.createQueue(capacity);
}
}
@Override
public synchronized void initialize() {
super.initialize();
ThreadPoolExecutor pool = this.getThreadPoolExecutor();
queue.setPool(pool);
policy.setPool(pool);
}
/**
* 当注册为全局Spring Bean时,Spring会默认调用此方法启动线程池。
* 但本实现方式,希望在类初始化时,就可使用,故重写为空方法。由自行调用{@link #initialize()}启动。
*/
@Override
public void afterPropertiesSet() {}
};
core = Math.max(core, 2); // 至少有 1 个以上核心线程常驻内存
exe.setCorePoolSize(core);
exe.setMaxPoolSize(Math.max(max, core));
exe.setKeepAliveSeconds((int) Math.max(keepAlive.getSeconds(), 2));
exe.setQueueCapacity(capacity);
exe.setThreadNamePrefix(preName);
exe.setRejectedExecutionHandler(policy);
exe.setWaitForTasksToCompleteOnShutdown(true);
exe.initialize();
return exe;
}
/**
* 获取队列中的任务类型
* @param queue {@link BlockingQueue}<{@link Runnable}>
* @return {@link List}<{@link Class}>
*/
public static List<Class<?>> tasks(BlockingQueue<Runnable> queue) {
Field[] fs = FutureTask.class.getDeclaredFields();
Field callable = getField("callable", fs);
if (callable != null) {
setAccessible(callable);
}
Object[] els = queue.toArray();
List<Class<?>> ts = Stream.of(els)
.map(el -> {
is: if (el instanceof FutureTask) {
if (callable == null) {
break is;
}
Callable<?> call;
try {
call = (Callable<?>) callable.get(el);
if (call == null) {
return null;
}
} catch (IllegalArgumentException | IllegalAccessException e) {
break is;
}
Class<?> c = call.getClass();
if (c.getName().startsWith(Executors.class.getName())) {
Optional<Field> optTask = Stream.of(c.getDeclaredFields())
.filter(fi -> "task".equals(fi.getName()) || fi.getType() == Runnable.class || fi.getType() == Callable.class)
.findFirst();
if (!optTask.isPresent()) {
return c;
}
Field fi = optTask.get();
setAccessible(fi);
try {
Object v = fi.get(call);
return Optional.ofNullable(v).map(Object::getClass).orElse(null);
} catch (IllegalArgumentException | IllegalAccessException e) {
return null;
}
}
return c;
} else if (el instanceof Runnable) {
return el.getClass();
}
return el.getClass();
})
.filter(Objects::nonNull)
.collect(Collectors.toList()); // .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
return ts;
}
/**
* 获取该属性
* @param name {@link String}:属性名;
* @param fs {@link Field}[]:属性组;
* @return {@link Field}:null - 不存在。
*/
static Field getField(String name, Field[] fs) {
for (int i = 0, len = fs.length; i < len; i++) {
Field f = fs[i];
if (f.getName().equals(name)) {
return f;
}
}
return null;
}
/**
* 访问该属性
* @param f {@link Field}:属性;
* @return {@link Boolean}:{@code true} - 成功。
*/
static boolean setAccessible(Field f) {
int mdf = f.getModifiers();
if (!Modifier.isPublic(mdf)) {
try {
f.setAccessible(true);
} catch (Exception e) {
return false;
}
}
return true;
}
/**
* 此队列意在快速执行队列积压任务。适用于少量核心线程被长时任务全部阻塞后,导致后续短时任务在队列中延迟等待<br/>
* 通过及时创建非核心线程(临时工)策略,快速消耗队列任务,消耗完成后,非核心线程超时自行销毁。<br/>
* <i>注:线程栈内存属堆外内存,会引起内存波动。但不影响JVM运行时堆内存。</i>
*/
public static class SpeedQueue<T> extends LinkedBlockingQueue<T> {
private static final long serialVersionUID = -1192196310907636323L;
private ThreadPoolExecutor pool;
public SpeedQueue() {
super(Short.MAX_VALUE);
}
public SpeedQueue(int capacity) {
super(capacity);
}
public ExecutorService getPool() {
return this.pool;
}
SpeedQueue<T> setPool(ThreadPoolExecutor pool) {
this.pool = pool;
return this;
}
@Override
public boolean offer(T e) {
int maxsz = this.pool.getMaximumPoolSize();
int sz = this.pool.getPoolSize();
if (sz >= maxsz) {
return super.offer(e);
}
int ac = this.pool.getActiveCount();
if (ac > 0 && ac >= sz) {
/* 因并发,pool.get*()返回的皆是近似值。当线程池达max时,会创建新线程失败,触发拒绝策略 */
return false; // 触发立即创建非核心线程
}
if (this.pool.getQueue().size() >= sz) {
return false;
}
return super.offer(e); // 无Thread可用(全池繁忙),则入队等待
}
}
/**
* 拒绝策略:尝试再次入队。<br/>
* 当{@link SpeedQueue#offer(Object)}触发拒绝策略,且线程池达最大值线程数后,此时队列中可能并无任务,因此要尝试再入队。
*/
public static class TryPolicy extends ThreadPoolExecutor.CallerRunsPolicy {
private ThreadPoolExecutor pool;
private Duration duration = Duration.ofMillis(10);
TryPolicy setPool(ThreadPoolExecutor pool) {
this.pool = pool;
return this;
}
public TryPolicy setDuration(Duration duration) {
this.duration = duration;
return this;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor exe) {
BlockingQueue<Runnable> queue = this.pool.getQueue();
try { // 可能是线程池全满触发拒绝,则再尝试入队
boolean offer = queue.offer(r, this.duration.toMillis(), TimeUnit.MILLISECONDS);
if (offer) {
return;
}
} catch (Exception e) {}
/* 等待入队失败,则触发真实意图的拒绝策略 */
super.rejectedExecution(r, exe);
}
}
}
测试示例
java
public static void main(String[] args) {
for (int i = 0; i < THREADS; i++) {
int n = i;
POOL0.submit(() -> {
System.out.println(n + " 线程池 - " + Thread.currentThread().getName());
if (n < POOL.getCorePoolSize()) // 模拟耗时任务阻塞整个线程池
java.util.concurrent.locks.LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
});
}
for (int i = 0; i < THREADS; i++) {
int n = i;
POOL.submit(() -> {
System.out.println(n + " Spring线程池 - " + Thread.currentThread().getName());
if (n < POOL.getCorePoolSize())
java.util.concurrent.locks.LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
});
}
System.out.printf("线程池:CPU - %s; 核心线程 - %s; 已开启线程 - %s / %s / %s; 队列积压 - %s;\r\n", THREADS, POOL0.getCorePoolSize(), POOL0.getActiveCount(), POOL0.getPoolSize(), POOL0.getMaximumPoolSize(), POOL0.getQueue().size());
System.out.printf("Spring线程池:CPU - %s; 核心线程 - %s; 已开启线程 - %s / %s / %s; 队列积压 - %s;\r\n", THREADS, POOL.getCorePoolSize(), POOL0.getActiveCount(), POOL.getPoolSize(), POOL.getMaxPoolSize(), POOL.getQueueSize());
POOL0.shutdown();
POOL.shutdown();
}
使用过程中,若出现bug,或有其它优化建议,欢迎在此文章"评论区"留言讨论,并留下您的邮箱,以便改正后及时通知您。