组件地址
<dependency>
<groupId>org.opengoofy.index12306</groupId>
<artifactId>index12306-common-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
组件功能
抽象常用枚举
package org.opengoofy.index12306.framework.starter.common.enums;
/**
* 删除标记枚举
*
*/
public enum DelEnum {
/**
* 正常状态
*/
NORMAL(0),
/**
* 删除状态
*/
DELETE(1);
private final Integer statusCode;
DelEnum(Integer statusCode) {
this.statusCode = statusCode;
}
public Integer code() {
return this.statusCode;
}
public String strCode() {
return String.valueOf(this.statusCode);
}
@Override
public String toString() {
return strCode();
}
}
封装线程池
封装快速消费线程池
参考Dubbo线程池实现快速消费线程池
针对如何解决 JDK 线程池中不超过最大线程数下即时快速消费任务,而不是在队列中堆积
业务是多变的,而 JDK 中的线程池消费流程却是固定的,所以 基于阻塞队列、线程池扩展改变了原有流程。
线程池参数
以 ThreadPoolExecutor#execute(Runnable runnable) 举例,这里先说下线程池的一些参数
线程池任务添加流程
本标题针对的就是第二步操作,即不放入阻塞队列,而直接创建非核心线程执行任务
Dubbo中的快速消费
Dubbo 中涉及到的类有两个,EagerThreadPoolExecutor、TaskQueue
TaskQueue 自定义阻塞队列
public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
...
// 队列中持有线程池的引用
private EagerThreadPoolExecutor executor;
public TaskQueue(int capacity) {
super(capacity);
}
public void setExecutor(EagerThreadPoolExecutor exec) {
executor = exec;
}
@Override
public boolean offer(Runnable runnable) {
...
// 获取线程池中线程数
int currentPoolThreadSize = executor.getPoolSize();
// 如果有核心线程正在空闲,将任务加入阻塞队列,由核心线程进行处理任务
if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
return super.offer(runnable);
}
/**
*【重点】当前线程池线程数量小于最大线程数
* 返回false,根据线程池源码,会创建非核心线程
*/
if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
return false;
}
// 如果当前线程池数量大于最大线程数,任务加入阻塞队列
return super.offer(runnable);
}
}
存在一个疑点,getSubmittedTaskCount() 是如何获取提交任务数量的,这里就需要看一下 EagerThreadPoolExecutor 实现了,也比较简单,只是 重写了线程池的两个方法: afterExecute()、execute() 。
EagerThreadPoolExecutor 封装快速消费线程池。
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
/**
* task count
*/
private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
/**
* @return current tasks which are executed
*/
public int getSubmittedTaskCount() {
return submittedTaskCount.get();
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
submittedTaskCount.decrementAndGet();
}
@Override
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
// do not increment in method beforeExecute!
submittedTaskCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
// retry to offer the task into queue.
final TaskQueue queue = (TaskQueue) super.getQueue();
try {
if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.", rx);
}
} catch (InterruptedException x) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} catch (Throwable t) {
// decrease any way
submittedTaskCount.decrementAndGet();
throw t;
}
}
}
EagerThreadPoolExecutor 继承了 ThreadPoolExecutor,在 execute() 上做了个性化设计。
并在线程池内新增了一个任务数量的字段,是一个原子类,添加任务时自增,任务异常及结束时递减
定义快速消费线程池的阻塞队列,不能使用原有的常用队列
package org.opengoofy.index12306.framework.starter.common.threadpool.support.eager;
import lombok.Setter;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 快速消费任务队列
*
*/
public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
@Setter
private EagerThreadPoolExecutor executor;
public TaskQueue(int capacity) {
super(capacity);
}
@Override
public boolean offer(Runnable runnable) {
int currentPoolThreadSize = executor.getPoolSize();
// 如果有核心线程正在空闲,将任务加入阻塞队列,由核心线程进行处理任务
if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
return super.offer(runnable);
}
// 当前线程池线程数量小于最大线程数,返回 False,根据线程池源码,会创建非核心线程
if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
return false;
}
// 如果当前线程池数量大于最大线程数,任务加入阻塞队列
return super.offer(runnable);
}
public boolean retryOffer(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if (executor.isShutdown()) {
throw new RejectedExecutionException("Executor is shutdown!");
}
return super.offer(o, timeout, unit);
}
}
定义快速消费线程池,这里参考了 Dubbo 的线程池模型之一。另外,Tomcat 也是这种消费模型
package org.opengoofy.index12306.framework.starter.common.threadpool.support.eager;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 快速消费线程池
*
* @公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
*/
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
public EagerThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
TaskQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
public int getSubmittedTaskCount() {
return submittedTaskCount.get();
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
submittedTaskCount.decrementAndGet();
}
@Override
public void execute(Runnable command) {
submittedTaskCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException ex) {
TaskQueue taskQueue = (TaskQueue) super.getQueue();
try {
if (!taskQueue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.", ex);
}
} catch (InterruptedException iex) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException(iex);
}
} catch (Exception ex) {
submittedTaskCount.decrementAndGet();
throw ex;
}
}
}
通过动态代理实现拒绝策略扩展
学习MyBatis动态代理扩展拒绝策略
任务拒绝流程
代理模式
代理模式(Proxy Design Pattern),在不改变原始类代码的情况下,引入代理类对原始类的功能作出增强
扩展线程池
先来创建个自定义线程池,继承原生 ThreadPoolExecutor。添加一个拒绝策略次数统计参数,并添加原子自增和查询方法。
public class SupportThreadPoolExecutor extends ThreadPoolExecutor {
/**
* 拒绝策略次数统计
*/
private final AtomicInteger rejectCount = new AtomicInteger();
public SupportThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
/**
* 设置 {@link SupportThreadPoolExecutor#rejectCount} 自增
*/
public void incrementRejectCount() {
rejectCount.incrementAndGet();
}
/**
* 获取拒绝次数
*
* @return
*/
public int getRejectCount() {
return rejectCount.get();
}
}
扩展拒绝策略
创建增强的公共拒绝策略,其中包含 拒绝策略次数统计 以及 报警推送,供实际的拒绝策略子类实现
public interface SupportRejectedExecutionHandler extends RejectedExecutionHandler {
/**
* 拒绝策略记录时, 执行某些操作
*
* @param executor
*/
default void beforeReject(ThreadPoolExecutor executor) {
if (executor instanceof SupportThreadPoolExecutor) {
SupportThreadPoolExecutor supportExecutor = (SupportThreadPoolExecutor) executor;
// 发起自增
supportExecutor.incrementRejectCount();
// 触发报警...
System.out.println("线程池触发了任务拒绝...");
}
}
}
public class SupportAbortPolicyRejected extends ThreadPoolExecutor.AbortPolicy implements SupportRejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
beforeReject(e);
super.rejectedExecution(r, e);
}
}
测试
@SneakyThrows
public static void main(String[] args) {
SupportThreadPoolExecutor executor = new SupportThreadPoolExecutor(
1,
1,
1024,
TimeUnit.SECONDS,
new LinkedBlockingQueue(1),
// 使用自定义拒绝策略
new SupportAbortPolicyRejected()
);
// 测试流程
for (int i = 0; i < 3; i++) {
try {
// 无限睡眠, 以此触发拒绝策略.(此处有异常, 为了减少无用代码, 省略...)
executor.execute(() -> Thread.sleep(Integer.MAX_VALUE));
} catch (Exception ignored) {
}
}
Thread.sleep(50);
System.out.println(String.format("线程池拒绝策略次数 :: %d", executor.getRejectCount()));
}
/**
* 日志打印:
*
* 线程池触发了任务拒绝...
* 线程池拒绝策略次数 :: 1
*/
根据日至打印得知,我们的扩展需求完整的实现了。当线程池执行任务拒绝行为时,首先会调用 SupportRejectedExecutionHandler#beforeReject,然后才是执行真正的拒绝策略行为
静态代理
动态代理
线程池使用代理类进行任务拒绝,测试代码如下
@SneakyThrows
public static void main(String[] args) {
// 删除 SupportThreadPoolExecutor 构造方法中的拒绝策略
SupportThreadPoolExecutor executor = new SupportThreadPoolExecutor(
1,
1,
1024,
TimeUnit.SECONDS,
new LinkedBlockingQueue(1)
);
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
// 创建拒绝策略代理类
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) Proxy.newProxyInstance(
abortPolicy.getClass().getClassLoader(),
abortPolicy.getClass().getInterfaces(),
new RejectedExecutionProxyInvocationHandler(abortPolicy, executor)
);
// 线程池 set 拒绝策略代理类
executor.setRejectedExecutionHandler(rejectedExecutionHandler);
// 测试流程
for (int i = 0; i < 3; i++) {
try {
// 无限睡眠, 以此触发拒绝策略.(此处有异常, 为了减少无用代码, 省略...)
executor.execute(() -> Thread.sleep(Integer.MAX_VALUE));
} catch (Exception ex) {
// ignore
}
}
Thread.sleep(50);
System.out.println(String.format("线程池拒绝策略次数 :: %d", executor.getRejectCount()));
}
/**
* 日志打印:
*
* 线程池触发了任务拒绝...
* 线程池拒绝策略次数 :: 1
*/
我们换一种思路去创建代理拒绝策略类,从外部的创建变更到内部就可以了;这一版选择在线程池的构造方法内部实现代理类。
public class SupportThreadPoolExecutor extends ThreadPoolExecutor {
// 省略代码...
public SupportThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
handler.getClass().getInterfaces(),
new RejectedExecutionProxyInvocationHandler(handler, this)
);
setRejectedExecutionHandler(rejectedExecutionHandler);
}
// 省略代码...
}
动态代理扩展知识
提前说明下,MyBatis 中使用的动态代理模式在线程池中并不适用,这里仅为了演示操作
public class SupportThreadPoolExecutor extends ThreadPoolExecutor {
// 省略代码...
public SupportThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) Proxy.newProxyInstance(
RejectedExecutionHandler.class.getClass().getClassLoader(),
new Class[]{RejectedExecutionHandler.class},
new RejectedExecutionProxyInvocationHandler(this)
);
setRejectedExecutionHandler(rejectedExecutionHandler);
}
// 省略代码...
}
@AllArgsConstructor
public class RejectedExecutionProxyInvocationHandler implements InvocationHandler {
private SupportThreadPoolExecutor executor;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
executor.incrementRejectCount();
System.out.println("线程池触发了任务拒绝...");
throw new RejectedExecutionException("Task rejected from.");
}
}
定义动态代理类
package org.opengoofy.index12306.framework.starter.common.threadpool.proxy;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicLong;
/**
* 线程池拒绝策略代理执行器
*
*/
@Slf4j
@AllArgsConstructor
public class RejectedProxyInvocationHandler implements InvocationHandler {
/**
* Target object
*/
private final Object target;
/**
* Reject count
*/
private final AtomicLong rejectCount;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
rejectCount.incrementAndGet();
try {
log.error("线程池执行拒绝策略, 此处模拟报警...");
return method.invoke(target, args);
} catch (InvocationTargetException ex) {
throw ex.getCause();
}
}
}
定义工具类
每次去创建动态代理类的方法也比较麻烦,我们提供一个动态代理类创建的工具类并附带单元测试
package org.opengoofy.index12306.framework.starter.common.threadpool.proxy;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.opengoofy.index12306.framework.starter.common.toolkit.ThreadUtil;
import java.lang.reflect.Proxy;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* 拒绝策略代理工具类
*
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RejectedProxyUtil {
/**
* 创建拒绝策略代理类
*
* @param rejectedExecutionHandler 真正的线程池拒绝策略执行器
* @param rejectedNum 拒绝策略执行统计器
* @return 代理拒绝策略
*/
public static RejectedExecutionHandler createProxy(RejectedExecutionHandler rejectedExecutionHandler, AtomicLong rejectedNum) {
// 动态代理模式: 增强线程池拒绝策略,比如:拒绝任务报警或加入延迟队列重复放入等逻辑
return (RejectedExecutionHandler) Proxy
.newProxyInstance(
rejectedExecutionHandler.getClass().getClassLoader(),
new Class[]{RejectedExecutionHandler.class},
new RejectedProxyInvocationHandler(rejectedExecutionHandler, rejectedNum));
}
/**
* 测试线程池拒绝策略动态代理程序
*/
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(1, 3, 1024, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1));
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
AtomicLong rejectedNum = new AtomicLong();
RejectedExecutionHandler proxyRejectedExecutionHandler = RejectedProxyUtil.createProxy(abortPolicy, rejectedNum);
threadPoolExecutor.setRejectedExecutionHandler(proxyRejectedExecutionHandler);
for (int i = 0; i < 5; i++) {
try {
threadPoolExecutor.execute(() -> ThreadUtil.sleep(100000L));
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
System.out.println("================ 线程池拒绝策略执行次数: " + rejectedNum.get());
}
}
常用工具类
package org.opengoofy.index12306.framework.starter.common.toolkit;
import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import com.github.dozermapper.core.loader.api.BeanMappingBuilder;
import lombok.NoArgsConstructor;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static com.github.dozermapper.core.loader.api.TypeMappingOptions.mapEmptyString;
import static com.github.dozermapper.core.loader.api.TypeMappingOptions.mapNull;
/**
* 对象属性复制工具类
*
*/
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class BeanUtil {
protected static Mapper BEAN_MAPPER_BUILDER;
static {
BEAN_MAPPER_BUILDER = DozerBeanMapperBuilder.buildDefault();
}
/**
* 属性复制
*
* @param source 数据对象
* @param target 目标对象
* @param <T>
* @param <S>
* @return 转换后对象
*/
public static <T, S> T convert(S source, T target) {
Optional.ofNullable(source)
.ifPresent(each -> BEAN_MAPPER_BUILDER.map(each, target));
return target;
}
/**
* 复制单个对象
*
* @param source 数据对象
* @param clazz 复制目标类型
* @param <T>
* @param <S>
* @return 转换后对象
*/
public static <T, S> T convert(S source, Class<T> clazz) {
return Optional.ofNullable(source)
.map(each -> BEAN_MAPPER_BUILDER.map(each, clazz))
.orElse(null);
}
/**
* 复制多个对象
*
* @param sources 数据对象
* @param clazz 复制目标类型
* @param <T>
* @param <S>
* @return 转换后对象集合
*/
public static <T, S> List<T> convert(List<S> sources, Class<T> clazz) {
return Optional.ofNullable(sources)
.map(each -> {
List<T> targetList = new ArrayList<T>(each.size());
each.stream()
.forEach(item -> targetList.add(BEAN_MAPPER_BUILDER.map(item, clazz)));
return targetList;
})
.orElse(null);
}
/**
* 复制多个对象
*
* @param sources 数据对象
* @param clazz 复制目标类型
* @param <T>
* @param <S>
* @return 转换后对象集合
*/
public static <T, S> Set<T> convert(Set<S> sources, Class<T> clazz) {
return Optional.ofNullable(sources)
.map(each -> {
Set<T> targetSize = new HashSet<T>(each.size());
each.stream()
.forEach(item -> targetSize.add(BEAN_MAPPER_BUILDER.map(item, clazz)));
return targetSize;
})
.orElse(null);
}
/**
* 复制多个对象
*
* @param sources 数据对象
* @param clazz 复制目标类型
* @param <T>
* @param <S>
* @return 转换后对象集合
*/
public static <T, S> T[] convert(S[] sources, Class<T> clazz) {
return Optional.ofNullable(sources)
.map(each -> {
@SuppressWarnings("unchecked")
T[] targetArray = (T[]) Array.newInstance(clazz, sources.length);
for (int i = 0; i < targetArray.length; i++) {
targetArray[i] = BEAN_MAPPER_BUILDER.map(sources[i], clazz);
}
return targetArray;
})
.orElse(null);
}
/**
* 拷贝非空且非空串属性
*
* @param source 数据源
* @param target 指向源
*/
public static void convertIgnoreNullAndBlank(Object source, Object target) {
DozerBeanMapperBuilder dozerBeanMapperBuilder = DozerBeanMapperBuilder.create();
Mapper mapper = dozerBeanMapperBuilder.withMappingBuilders(new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(source.getClass(), target.getClass(), mapNull(false), mapEmptyString(false));
}
}).build();
mapper.map(source, target);
}
/**
* 拷贝非空属性
*
* @param source 数据源
* @param target 指向源
*/
public static void convertIgnoreNull(Object source, Object target) {
DozerBeanMapperBuilder dozerBeanMapperBuilder = DozerBeanMapperBuilder.create();
Mapper mapper = dozerBeanMapperBuilder.withMappingBuilders(new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(source.getClass(), target.getClass(), mapNull(false));
}
}).build();
mapper.map(source, target);
}
}