Spring中的几种线程池使用总结

149 篇文章 2 订阅
148 篇文章 0 订阅

第一种:常用的Executor线程池

package com.glodon.litddemo.config.threadPool;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
 * @className: ThreadExecutorPool.java
 * @description: TODO 线程池使用
 * @author: litd
 * @date: 2021/7/20 17:00
 **/
@Configuration
@EnableAsync
public class ThreadExecutorPool {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Bean("asyncServiceExecutor")
    public ThreadPoolTaskExecutor asyncServiceExecutor() {
        log.info("start asyncServiceExecutorCulateChild");
        int coreCpuNum = (Runtime.getRuntime().availableProcessors());
        int maxPoolSize = (Runtime.getRuntime().availableProcessors())*2;
        //使用自定义的VisiableThreadPoolTaskExecutor
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(coreCpuNum);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(99999999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("j-pool-");
        executor.setKeepAliveSeconds(180);

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setTaskDecorator(runnable -> runnable);
        //执行初始化
        executor.initialize();
        return executor;
    }

    /**
     * @description: 只负责执行,不带返回值
     * @author: litd
     * @date: 2021/7/22 16:07
     **/
    @Bean("Executor-thread-void")
    public Executor ExecutorVoid() {
        int coreCpuNum = (Runtime.getRuntime().availableProcessors());
        int maxPoolSize = (Runtime.getRuntime().availableProcessors())*2;
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(coreCpuNum);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(10000);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("Executor-thread-");
        executor.setKeepAliveSeconds(60);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setTaskDecorator(runnable -> runnable);
        //执行初始化
        executor.initialize();
        return executor;
    }

    /**
     * @description: 单例线程池,主要用例打印频繁的日志和独立执行不重要的任务。不阻塞主程序的性能
     * @author: litd
     * @date: 2021/7/23 9:48
     **/
    @Bean("singleThreadExecutor")
    public ExecutorService singleThreadExecutor(){
        ExecutorService singleThreadExecutor= new ThreadPoolExecutor(1,1,15, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(200000),
                new ThreadPoolExecutor.CallerRunsPolicy());
        return singleThreadExecutor;
    }

第二种:装饰器线程池(采用goole提供的Guava工具包实现)

package com.glodon.litddemo.config.threadPool;

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;


import java.util.concurrent.*;

/**
 * @author
 * @version 1.0
 * @description
 * @updateRemark
 * @updateUser
 * @createDate 2021/7/20 18:03
 * @updateDate 2021/7/20 18:03
 **/
@Configuration
@EnableAsync
public class ThreadPoolGuava {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * 线程工厂名称
     */
    private static final ThreadFactory FACTORY = new BasicThreadFactory.Builder().namingPattern("guava-pool-")
            .daemon(false).build();
    @Bean("ExecutorGuava")
    public ListeningExecutorService  asyncServiceExecutor() {
        log.info("start asyncServiceExecutorCulateChild");
        //使用自定义的VisiableThreadPoolTaskExecutor
        ThreadPoolExecutor  executor = new ThreadPoolExecutor(10,20,120, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(200),
                new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadFactory(FACTORY);

        ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executor);
        return listeningExecutorService;
    }
}

以上两种的使用方式和区别

public class ExampleController1 {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    @Qualifier("asyncServiceExecutor")
    private ThreadPoolTaskExecutor executor;
    @Autowired
    @Qualifier("ExecutorGuava")
    private ListeningExecutorService listeningExecutorService;

    public String threadTest(){

        try {
            Future<Integer> submit = executor.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    /**
                     * 业务实现
                     */

                    return 66;
                }
            });
            Integer integer = submit.get();
            log.info(integer.toString());
        }catch (Exception e){
            e.printStackTrace();
        }

        try {
            //threadService.bbs(990)  代表业务实现,需要异步执行的业务
            ListenableFuture<Integer> submit = listeningExecutorService.submit(() -> threadService.bbs(990));
            //异步监听任务是否执行成功,如果不需要监听,可以不写
            Futures.addCallback(submit, new FutureCallback<Integer>() {
                @Override
                public void onSuccess(@Nullable Integer integer) {
                   log.info("装饰线程执行成功了");
                }

                @Override
                public void onFailure(Throwable throwable) {
                    System.out.println("执行失败了,重新调用");
                    //threadService.bbs(990);
                }
            },listeningExecutorService);
            Integer integer = submit.get();
            log.info("获得最终结果为:"+integer);
        }catch (Exception e){
            e.printStackTrace();
        }
}

区别:

第一种,属于Spring自带,传统的jdk封装实现,对线程的异步执行后的结果,需要自己实现监听或者阻塞,相对麻烦。

第二种,装饰线程池,其原理也是实现的第一种,只不过他的好处是更便捷,尤其对线程的监听,随时随地可以监听此线程任务的内部状态,做出相应的处理。在使用上,更为推荐这种方式。

第三种:ForkJoinPool线程池。

ForkJoinPool 不是为了替代 ExecutorService,而是它的补充,在某些应用场景下性能比 ExecutorService 更好。

ForkJoinPool 主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数,例如 quick sort 等。

ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时。

ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务(ForkJoinTask)。

每个工作线程在运行中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,使用的是 LIFO 方式来执行。

每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其他工作线程的工作队列),窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方式。

在遇到 join() 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成。

从以上分析可以看出,再CPU物理线程数满足的情况下,它的性能是最好的。

事例:

/**
 * @className: ForkJoinPoolFiber.java
 * @description: 并行流线程池
 * @author: litd
 * @date: 2021/8/4 15:15
 **/
public class ForkJoinPoolFiber {

    private ForkJoinPoolFiber(){}

    //这是默认的方式。当然你也可以自己创建,如:ForkJoinPool pool = new ForkJoinPool(16); 16代表初始化的线程数
    private static ForkJoinPool pool =ForkJoinPool.commonPool();
    public static ForkJoinPool getForkJoinPool(){
        if(pool==null){
            pool =ForkJoinPool.commonPool();
        }
        return pool;
    }


    /**
     * @description: TODO 使用demo
     * @author: litd
     * @date: 2021/8/4 15:20
     **/
    static public void forkjoinTest() throws Exception{
        long time=System.currentTimeMillis();
        ForkJoinPool pool =ForkJoinPoolFiber.getForkJoinPool();
        List<ForkJoinTask<Integer>> fiberQueue = new ArrayList<>();
        AtomicInteger sum= new AtomicInteger();
        for (int j = 0; j < 200000; j++) {
            int finalI = j;
            ForkJoinTask<Integer> submit = pool.submit(() -> {
                sum.addAndGet(finalI);
                return sum.get();
            });
            fiberQueue.add(submit);
        }
        fiberQueue.forEach(item ->{
            try {
                item.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long time2=System.currentTimeMillis();
    }

}

第四种:CompletableFuture 属于线程模板执行器。

1,Java8新增了CompletableFuture 提供对异步计算的支持,可以通过回调的方式处理计算结果

2,它也是一个线程框架,异步执行器。它默认使用的线程池为ForkJoinPool ,可以默认指定ExecutorService接口下的线程池

CompletableFuture用来解决什么问题?

1,解决future模式的缺点

1)为了解决Future虽然可以实现异步获取线程的执行结果,但是future没有提供通知机制,调用方无法得知future什么时候执行完的问题。

2)要么使用阻塞, 在future.get()的地方等待future返回结果,这时会变成同步操作。如果使用isDone()方法进行循环判断,就会消耗cpu资源

2,CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数。

3,他避免了传统回调的最大问题,就是能够将控制流分离到不同的事件处理器中

事例:

/**
 * @className: CompletableFutureAsync.java
 * @description: TODO 类描述
 * @author: litd
 * @date: 2021/8/10 16:26
 **/
public class CompletableFutureAsync {

    private static final Logger logger = LogManager.getLogger(CompletableFutureAsync.class);

    public static void main(String[] args) throws Exception {
        completableTest();
    }

    /**
    * @Description  异步回调
    * @Author  litd
    * @Date   2021/8/10 16:33
    * @Param
    * @Return
    * @Exception
    */
    public static void completableTest() throws Exception{
        ForkJoinPool pool=new ForkJoinPool(16);
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 3.9;
        },pool);


        CompletableFuture cf2=cf.thenApply(result ->{
            //将上一个任务的返回结果作为参数传入,其实是创建了一个新的CompletableFuture执行
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.error("cf2得到上一个任务的结果={}",result);
            String str = result+"cf1结果经过cf2进行了加工";
            return str;
        }).thenAccept(result->{
            logger.error("得到上一个任务的返回值={},但我的就是貔貅只进不出!",result);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).thenRun(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.error("我是被遗忘的那个,不给狗粮,我也不拉狗屎");
        });
        logger.error("我是不是比cf2还执行的早");
        logger.error("cf2执行完成了,得到的结果={}",cf2.get());

        //当某个任务异常时进行处理
        CompletableFuture<Double> cf3=cf.exceptionally(ex ->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.error("cf婚内出轨了,被我处理了,哈哈");
            ex.printStackTrace();
            return -2.3;
        });
    }


}

大家可通过简单的demo验证进行掌握

以上集中线程再使用上相对有区别,从性能的角度来说,基本都差不多。但是如果是是IO密集型的处理,建议采用ForkJoin线程池。计算密集型可以采用其他三种。性能差距不大,都是和CPU进行的交互。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值