手写精简版动态线程池

前言

  • 项目启动后,我们可能需要增加线程数或缩小线程数。一般情况下,需要重启服务才会生效。本篇文章介绍一种修改线程池后不用重启服务的办法。当然这也归功于JUC工具包自身的强大。

技术栈

  • Nacos 2.1.0

监听配置变更,然后修改线程池配置。类似发布订阅的模式,这块用其他也行,比如Redis。

  • Spring Boot 2.7.3

项目主框架,不必多说。

实现逻辑

  • 监听Nacos变更,然后修改线程池配置。
  • 比如修改corePoolSize,最后还是依靠ThreadPoolExecutor#setCorePoolSize去修改。
    public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0)
            throw new IllegalArgumentException();
            // 与当前核心线程数差值
        int delta = corePoolSize - this.corePoolSize;
        this.corePoolSize = corePoolSize;
        // 如果当前存在线程比设置的数值大
        if (workerCountOf(ctl.get()) > corePoolSize)
        	// 中断一些核心线程
            interruptIdleWorkers();
        else if (delta > 0) { // 如果当前线程少了
            // We don't really know how many new threads are "needed".
            // As a heuristic, prestart enough new workers (up to new
            // core size) to handle the current number of tasks in
            // queue, but stop if queue becomes empty while doing so.
            int k = Math.min(delta, workQueue.size());
            // 添加核心线程
            while (k-- > 0 && addWorker(null, true)) {
                if (workQueue.isEmpty())
                    break;
            }
        }
    }

代码

  • 线程池对象DynamicThreadPoolExecutor.java
package com.sample.threadpool;

import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author Zhou Zhongqing
 * @ClassName DynamicThreadPoolProperties
 * @description: 封装线程池
 * @date 2022-09-25 15:09
 */
public class DynamicThreadPoolExecutor extends ThreadPoolTaskExecutor {


    // 队列最大长度
    private int queueCapacity = 1000;

    // 线程池维护线程所允许的空闲时间
    private int keepAliveSeconds = 300;

    public DynamicThreadPoolExecutor(int corePoolSize, int maxPoolSize, String threadNamePrefix) {
        this.setMaxPoolSize(maxPoolSize);
        this.setCorePoolSize(corePoolSize);
        this.setQueueCapacity(queueCapacity);
        this.setKeepAliveSeconds(keepAliveSeconds);
        // 线程池对拒绝任务(无线程可用)的处理策略
        this.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //线程名称前缀
        this.setThreadNamePrefix(threadNamePrefix + "-");
    }
}


  • 配置内容的对象DynamicThreadPoolProperties.java
package com.sample.properties;

import com.alibaba.nacos.api.config.convert.NacosConfigConverter;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ByteArrayResource;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;


/**
 * @author Zhou Zhongqing
 * @ClassName DynamicThreadPoolProperties
 * @description: 配置内容的对象
 * @date 2022-09-25 15:09
 */
public class DynamicThreadPoolProperties implements NacosConfigConverter {

    private List<ExecutorProperties> executors;


    @Override
    public String toString() {
        return "DynamicThreadPoolProperties{" +
                "executors=" + executors +
                '}';
    }

    public List<ExecutorProperties> getExecutors() {
        return executors;
    }

    public void setExecutors(List<ExecutorProperties> executors) {
        this.executors = executors;
    }

    @Override
    public boolean canConvert(Class targetType) {
        return true;
    }

    // 转换字符串配置为对象
    @Override
    public Object convert(String config) {
        // 读取配置并转换成对象

        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources(new ByteArrayResource(config.getBytes(StandardCharsets.UTF_8)));
        Properties properties = yaml.getObject();

        DynamicThreadPoolProperties dynamicThreadPoolProperties = new DynamicThreadPoolProperties();
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(source);
        ResolvableType resolvableType = ResolvableType.forClass(DynamicThreadPoolProperties.class);
        Bindable<Object> objectBindable = Bindable.of(resolvableType).withExistingValue(dynamicThreadPoolProperties);
        binder.bind("dynamic-thread-pool", objectBindable);
        return dynamicThreadPoolProperties;
    }

    public static class ExecutorProperties {


        private String name;

        private Integer corePoolSize = 5;

        private Integer maximumPoolSize = 10;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getCorePoolSize() {
            return corePoolSize;
        }

        public void setCorePoolSize(Integer corePoolSize) {
            this.corePoolSize = corePoolSize;
        }

        public Integer getMaximumPoolSize() {
            return maximumPoolSize;
        }

        public void setMaximumPoolSize(Integer maximumPoolSize) {
            this.maximumPoolSize = maximumPoolSize;
        }

        @Override
        public String toString() {
            return "ExecutorProperties{" +
                    "name='" + name + '\'' +
                    ", corePoolSize=" + corePoolSize +
                    ", maximumPoolSize=" + maximumPoolSize +
                    '}';
        }
    }
}

  • 监听Nacos配置变化 NacosListener.java
package com.sample.listener;
import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.sample.properties.DynamicThreadPoolProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;


/**
 * @author Zhou Zhongqing
 * @ClassName NacosListener
 * @description: 监听Nacos配置变化
 * @date 2022-09-25 15:09
 */
@Component
public class NacosListener {

    @Resource
    private ApplicationContext applicationContext;


    @NacosConfigListener(dataId = "dynamic-thread-pool.yaml", converter = DynamicThreadPoolProperties.class)
    public void onReceived(DynamicThreadPoolProperties properties) {
        System.out.println("onReceived " + properties);

        // 循环设置线程池的核心线程数和最大线程数
        List<DynamicThreadPoolProperties.ExecutorProperties> executorProperties = properties.getExecutors();
        for (DynamicThreadPoolProperties.ExecutorProperties executorProperty : executorProperties) {
            ThreadPoolTaskExecutor threadPoolTaskExecutor = applicationContext.getBean(executorProperty.getName(), ThreadPoolTaskExecutor.class);

            System.out.println(executorProperty.getName() + " 重新配置 核心线程数: " + executorProperty.getCorePoolSize() + " 最大线程数 :" + executorProperty.getMaximumPoolSize());
            threadPoolTaskExecutor.setCorePoolSize(executorProperty.getCorePoolSize());
            threadPoolTaskExecutor.setMaxPoolSize(executorProperty.getMaximumPoolSize());
        }
    }


}

这个功能用ImportBeanDefinitionRegistrar接口也是可以的,然后用@Import导入。可以参考本人拙作 https://sample.blog.csdn.net/article/details/90613661

package com.sample.threadpool;

import com.sample.properties.DynamicThreadPoolProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.util.List;


/**
 * @author Zhou Zhongqing
 * @ClassName RegisterThreadPool
 * @description: 注册线程池注册到Spring。
 * 这个功能用ImportBeanDefinitionRegistrar接口也是可以的,然后用@Import导入。可以参考本人拙作 https://sample.blog.csdn.net/article/details/90613661
 * @date 2022-09-25 15:09
 */
@Component
public class RegisterThreadPoolBean implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {


    private Environment environment;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {

        // 读取配置
        DynamicThreadPoolProperties dynamicThreadPoolProperties = new DynamicThreadPoolProperties();
        Binder binder = Binder.get(environment);
        ResolvableType resolvableType = ResolvableType.forClass(DynamicThreadPoolProperties.class);
        Bindable<Object> objectBindable = Bindable.of(resolvableType).withExistingValue(dynamicThreadPoolProperties);
        binder.bind("dynamic-thread-pool", objectBindable);

        List<DynamicThreadPoolProperties.ExecutorProperties> executorProperties = dynamicThreadPoolProperties.getExecutors();
        for (DynamicThreadPoolProperties.ExecutorProperties executorProperty : executorProperties) {
            GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
            genericBeanDefinition.setBeanClass(DynamicThreadPoolExecutor.class);
            // 核心线程数
            genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getCorePoolSize());
            // 最大线程数
            genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getMaximumPoolSize());
            // 线程名称前缀
            genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getName());
            // 注册Bean
            beanDefinitionRegistry.registerBeanDefinition(executorProperty.getName(), genericBeanDefinition);
            System.out.println("注册 " + executorProperty.getName() + " 线程池");
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

  • 测试接口TestController.java
package com.sample.controller;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author Zhou Zhongqing
 * @ClassName TestController
 * @description: 测试
 * @date 2022-09-25 15:09
 */
@RestController
public class TestController {

    @Resource
    private ThreadPoolTaskExecutor orderThreadPool;

    @Resource
    private ThreadPoolTaskExecutor smsThreadPool;

    @RequestMapping("/test")
    public String test() {
        StringBuilder sb = new StringBuilder();

        orderThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行任务");
        });


        smsThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行任务");
        });

        sb.append("orderThreadPool ");
        sb.append(" 核心线程数 " + orderThreadPool.getCorePoolSize());
        sb.append(" 最大线程数 " + orderThreadPool.getMaxPoolSize());
        sb.append("<br />");

        sb.append("smsThreadPool ");
        sb.append(" 核心线程数 " + smsThreadPool.getCorePoolSize());
        sb.append(" 最大线程数 " + smsThreadPool.getMaxPoolSize());

        return sb.toString();
    }

}

  • nacos配置dynamic-thread-pool.yaml
dynamic-thread-pool:
  executors:
    - name: orderThreadPool
      core-pool-size: 5
      maximum-pool-size: 10
    - name: smsThreadPool
      core-pool-size: 5
      maximum-pool-size: 10

效果

在这里插入图片描述

  • 修改配置后,无需重启服务,再次请求接口。
    在这里插入图片描述

小结

posted on 2022-12-09 08:59  愤怒的苹果ext  阅读(347)  评论(0编辑  收藏  举报

导航