线程池【工具类】

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,或有其它优化建议,欢迎在此文章"评论区"留言讨论,并留下您的邮箱,以便改正后及时通知您。

相关推荐
深盾科技2 小时前
融合C++与Python:兼顾开发效率与运行性能
java·c++·python
我待_JAVA_如初恋2 小时前
idea创建MavenJavaWeb项目以后,包结构缺java
java·ide·intellij-idea
来深圳2 小时前
leetcode 739. 每日温度
java·算法·leetcode
CC大煊2 小时前
【java】Druid数据库连接池完整配置指南:从入门到生产环境优化
java·数据库·springboot
JIngJaneIL2 小时前
基于java+ vue交友系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·交友
苹果酱05672 小时前
解决linux mysql命令 bash: mysql: command not found 的方法
java·vue.js·spring boot·mysql·课程设计
程序员飞哥3 小时前
这样做的幂等也太全了吧
java·后端·spring
虫小宝3 小时前
返利软件架构设计:多平台适配的抽象工厂模式实践
java·开发语言·抽象工厂模式