实现公共组件库

151 阅读8分钟

组件地址

<dependency>
  <groupId>org.opengoofy.index12306</groupId>
  <artifactId>index12306-common-spring-boot-starter</artifactId>
  <version>${project.version}</version>
</dependency>

组件功能

image.png

抽象常用枚举

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) 举例,这里先说下线程池的一些参数

image.png

线程池任务添加流程

image.png

本标题针对的就是第二步操作,即不放入阻塞队列,而直接创建非核心线程执行任务

Dubbo中的快速消费

Dubbo 中涉及到的类有两个,EagerThreadPoolExecutorTaskQueue

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动态代理扩展拒绝策略

image.png

任务拒绝流程

image.png

image.png

代理模式

代理模式(Proxy Design Pattern),在不改变原始类代码的情况下,引入代理类对原始类的功能作出增强

image.png

扩展线程池

先来创建个自定义线程池,继承原生 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,然后才是执行真正的拒绝策略行为

静态代理

image.png

动态代理

image.png

image.png

线程池使用代理类进行任务拒绝,测试代码如下

@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
 */

image.png

我们换一种思路去创建代理拒绝策略类,从外部的创建变更到内部就可以了;这一版选择在线程池的构造方法内部实现代理类。

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);
    }

    // 省略代码...
}
动态代理扩展知识

image.png 提前说明下,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.");
    }
}

image.png

定义动态代理类
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);
    }
}