如何优雅的使用和理解线程池(下)

简介: 平时接触过多线程开发的童鞋应该都或多或少了解过线程池,之前发布的《阿里巴巴 Java 手册》里也有一条

SpringBoot 使用线程池


2018 年了,SpringBoot 盛行;来看看在 SpringBoot 中应当怎么配置和使用线程池。


既然用了 SpringBoot ,那自然得发挥 Spring 的特性,所以需要 Spring 来帮我们管理线程池:


@Configuration
public class TreadPoolConfig {
    /**
     * 消费队列线程
     * @return
     */
    @Bean(value = "consumerQueueThreadPool")
    public ExecutorService buildConsumerQueueThreadPool(){
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("consumer-queue-thread-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
        return pool ;
    }
}


使用时:


@Resource(name = "consumerQueueThreadPool")
    private ExecutorService consumerQueueThreadPool;
    @Override
    public void execute() {
        //消费队列
        for (int i = 0; i < 5; i++) {
            consumerQueueThreadPool.execute(new ConsumerQueueThread());
        }
    }


其实也挺简单,就是创建了一个线程池的 bean,在使用时直接从 Spring 中取出即可。


监控线程池


谈到了 SpringBoot,也可利用它 actuator 组件来做线程池的监控。


线程怎么说都是稀缺资源,对线程池的监控可以知道自己任务执行的状况、效率等。


关于 actuator 就不再细说了,感兴趣的可以看看这篇,有详细整理过如何暴露监控端点。


其实 ThreadPool 本身已经提供了不少 api 可以获取线程状态:



很多方法看名字就知道其含义,只需要将这些信息暴露到 SpringBoot 的监控端点中,我们就可以在可视化页面查看当前的线程池状态了。


甚至我们可以继承线程池扩展其中的几个函数来自定义监控逻辑:



看这些名称和定义都知道,这是让子类来实现的。


可以在线程执行前、后、终止状态执行自定义逻辑。


线程池隔离


线程池看似很美好,但也会带来一些问题。


如果我们很多业务都依赖于同一个线程池,当其中一个业务因为各种不可控的原因消耗了所有的线程,导致线程池全部占满。


这样其他的业务也就不能正常运转了,这对系统的打击是巨大的。


比如我们 Tomcat 接受请求的线程池,假设其中一些响应特别慢,线程资源得不到回收释放;线程池慢慢被占满,最坏的情况就是整个应用都不能提供服务。


所以我们需要将线程池进行隔离


通常的做法是按照业务进行划分:


比如下单的任务用一个线程池,获取数据的任务用另一个线程池。这样即使其中一个出现问题把线程池耗尽,那也不会影响其他的任务运行。


hystrix 隔离


这样的需求 Hystrix 已经帮我们实现了。


Hystrix 是一款开源的容错插件,具有依赖隔离、系统容错降级等功能。


下面来看看 Hystrix 简单的应用:


首先需要定义两个线程池,分别用于执行订单、处理用户。


/**
 * Function:订单服务
 *
 * @author crossoverJie
 *         Date: 2018/7/28 16:43
 * @since JDK 1.8
 */
public class CommandOrder extends HystrixCommand<String> {
    private final static Logger LOGGER = LoggerFactory.getLogger(CommandOrder.class);
    private String orderName;
    public CommandOrder(String orderName) {
        super(Setter.withGroupKey(
                //服务分组
                HystrixCommandGroupKey.Factory.asKey("OrderGroup"))
                //线程分组
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("OrderPool"))
                //线程池配置
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                        .withCoreSize(10)
                        .withKeepAliveTimeMinutes(5)
                        .withMaxQueueSize(10)
                        .withQueueSizeRejectionThreshold(10000))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
        )
        ;
        this.orderName = orderName;
    }
    @Override
    public String run() throws Exception {
        LOGGER.info("orderName=[{}]", orderName);
        TimeUnit.MILLISECONDS.sleep(100);
        return "OrderName=" + orderName;
    }
}
/**
 * Function:用户服务
 *
 * @author crossoverJie
 *         Date: 2018/7/28 16:43
 * @since JDK 1.8
 */
public class CommandUser extends HystrixCommand<String> {
    private final static Logger LOGGER = LoggerFactory.getLogger(CommandUser.class);
    private String userName;
    public CommandUser(String userName) {
        super(Setter.withGroupKey(
                //服务分组
                HystrixCommandGroupKey.Factory.asKey("UserGroup"))
                //线程分组
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("UserPool"))
                //线程池配置
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                        .withCoreSize(10)
                        .withKeepAliveTimeMinutes(5)
                        .withMaxQueueSize(10)
                        .withQueueSizeRejectionThreshold(10000))
                //线程池隔离
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
        )
        ;
        this.userName = userName;
    }
    @Override
    public String run() throws Exception {
        LOGGER.info("userName=[{}]", userName);
        TimeUnit.MILLISECONDS.sleep(100);
        return "userName=" + userName;
    }
}


api 特别简洁易懂,具体详情请查看官方文档。

然后模拟运行:

public static void main(String[] args) throws Exception {
        CommandOrder commandPhone = new CommandOrder("手机");
        CommandOrder command = new CommandOrder("电视");
        //阻塞方式执行
        String execute = commandPhone.execute();
        LOGGER.info("execute=[{}]", execute);
        //异步非阻塞方式
        Future<String> queue = command.queue();
        String value = queue.get(200, TimeUnit.MILLISECONDS);
        LOGGER.info("value=[{}]", value);
        CommandUser commandUser = new CommandUser("张三");
        String name = commandUser.execute();
        LOGGER.info("name=[{}]", name);
    }
复制代码


运行结果:



可以看到两个任务分成了两个线程池运行,他们之间互不干扰。


获取任务任务结果支持同步阻塞和异步非阻塞方式,可自行选择。


它的实现原理其实容易猜到:


利用一个 Map 来存放不同业务对应的线程池。


通过刚才的构造函数也能证明:



还要注意的一点是:


自定义的 Command 并不是一个单例,每次执行需要 new 一个实例,不然会报 This instance can only be executed once. Please instantiate a new instance. 异常。


总结


池化技术确实在平时应用广泛,熟练掌握能提高不少效率。


文末的 hystrix 源码:


github.com/crossoverJi…


相关文章
|
2月前
|
消息中间件 Kubernetes Java
记两个有关线程池的小问题
最近小伙伴们找我查的问题里,有两个与线程池相关的,最终都是花了一些时间才揪出原因所在,做一下记录。
43 1
|
3月前
|
监控 Java API
如何快速地实现一个线程池
如何快速地实现一个线程池
26 0
|
4月前
|
Java 调度
基于C++11的线程池
基于C++11的线程池
|
7月前
|
缓存 Java API
厉害了,线程池就该这么玩
厉害了,线程池就该这么玩
65 0
|
存储 Java 测试技术
13.一文彻底了解线程池
大家好,我是王有志。线程池是Java面试中必问的八股文,涉及到非常多的问题,今天我们就通过一篇文章,来彻底搞懂Java面试中关于线程池的问题。
408 2
13.一文彻底了解线程池
|
缓存 算法 Java
线程池和使用
线程池是一种用于管理和复用线程的机制。在多线程应用程序中,线程的创建和销毁需要消耗大量的系统资源,而线程池可以通过预先创建一定数量的线程,然后将任务分配给这些线程来避免频繁地创建和销毁线程,从而提高应用程序的性能和效率。线程池还可以控制并发线程的数量,避免过多的线程竞争资源导致的性能下降和系统崩溃。线程池是多线程编程中常用的一种技术,被广泛应用于各种类型的应用程序中。
80 0
线程池和使用
|
存储 Java 调度
线程池使用
线程池使用
KeyAffinityExecutor 线程池
KeyAffinityExecutor 线程池
|
消息中间件 监控 搜索推荐
线程池:我是谁?我在哪儿?
大家好,这篇文章跟大家探讨下日常使用线程池的各种姿势,重点介绍怎么在 Spring 环境中正确使用线程池。
线程池:我是谁?我在哪儿?
|
存储 监控 Java
线程池
线程池
134 0