怎么设置java线程池保证处理任务不丢失

备注
  1. 以下代码没有shutdown线程池
  2. Java,spring线程池同理
前提
  1. 创建一个线程池,线程池大小固定为10,阻塞队列大小为10,最大线程池为20,拒绝策略为默认AbortPolicy。
  2. 分页处理,有100页任务需要处理,需要处理100次
1. 常见固定线程池处理
    public static void main(String[] args) {
        
        ExecutorService executorService = new ThreadPoolExecutor(10, 20,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10));

        //假如要分页处理100页数据
        int totalPage = 100;
        
        //一共10个线程
        int totalThreadNum = 10;
        
        //直接开10个固定线程去处理任务
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                     
                /**
                 *   线程1,查询,处理1-10页
                 *   线程2,查询,处理11-20页
                 *   线程3,查询,处理21-30页
                 *   ....
                 */
            });
        }
    }

以上代码,开了10个固定线程,每个线程查询,处理10页数据。

如果我们的场景是需要要将100页数据从主线程提交到线程池中处理,而不是上述在线程池中直接查询100页数据进行处理,该怎么办呢?

2. 提交到线程池处理

我们可能会如下处理:

    public static void main(String[] args) {

        ExecutorService executorService = new ThreadPoolExecutor(10, 20,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10));

        int totalPage = 100;
        int totalThreadNum = 10;


        //直接把100页提交到线程池
        executorService.submit(() -> {
            try {
                //任务需要处理1秒钟
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(a);
        });
    }

运行结果如下:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@567d299b rejected from java.util.concurrent.ThreadPoolExecutor@2eafffde[Running, pool size = 20, active threads = 20, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
	at com.iboxpay.business.strategy.impl.TestService.main(TestService.java:25)
0
5
1

由线程池的基础原理。我们得知,其实上述线程,在并发太大的情况下,已经执行了拒绝策略,相应的任务就已经丢弃了。

这肯定不是我们要的结果,我们肯定是希望100页数据全部执行完成。

这时候我们肯定会,怎么让任务一页一页执行,不丢弃呢?

当线程池满了,阻塞后续进入的线程,让其等待?没错,这是个很好的解决思路。

3. 线程池满了,阻塞线程,慢慢等待
    public static void main(String[] args) {

        ExecutorService executorService = new ThreadPoolExecutor(10, 20,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10), new RejectedExecutionHandler() {
            //变更点,注意,此处把线程池中的阻塞队列拿出来,重新put Runnable
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                try {
                    executor.getQueue().put(r);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        int totalPage = 100;
        int totalThreadNum = 10;


        //直接把100页提交到线程池
        for (int i = 0; i < totalPage; i++) {

            final int a = i;
            executorService.submit(() -> {
                try {
                    //任务需要处理1秒钟
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(a);
            });
        }
    }

上述代码运行就可以把所有任务执行完成(不展示运行结果了)。

  1. 仔细查看上述代码变更点。仅仅做了一步操作,就是把线程池中的阻塞队列拿出来,重新把这个任务放进去。

  2. 这样有什么用?

  3. 我们来看一下线程池ThreadPoolExecutor的源码,仅仅看下面注释的部分

     public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
    
            int c = ctl.get();
            if (workerCountOf(c) < corePoolSize) {
                if (addWorker(command, true))
                    return;
                c = ctl.get();
            }
         	//此处,把任务放到阻塞队列中,采取的是offer方法
            if (isRunning(c) && workQueue.offer(command)) {
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(command))
                    reject(command);
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            else if (!addWorker(command, false))
                reject(command);
        }
    

    我们自己的拒绝策略是用的put方法,而线程池是用的offer方法。差别就在于offer方法是不阻塞的,插入不了了,就往下走;而put方法是一直阻塞,直到元素插入到阻塞队列中

    更详细的阻塞队列的插入和获取元素的不同方法及其原理可以自行了解一下

4. 其他提交线程池阻塞方式

待下一篇分析rocketmq是如何固定线程池并发消费
RocketMQ并发消费如何固定线程池

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
回答:Java线程池Java中的一个重点知识,并且在Java的工作中经常会遇到,因此在面试中也是必问的面试题目。以下是一些常见的Java线程池面试题: 1. 谈谈什么是线程池? 2. 为什么要使用线程池? 3. 你们哪些地方会使用到线程池? 4. 线程池有哪些作用? 5. 线程池的创建方式有哪些? 6. 线程池底层是如何实现复用的? 7. ThreadPoolExecutor核心参数有哪些? 8. 线程池创建的线程会一直在运行状态吗? 9. 为什么阿里巴巴不建议使用Executors? 10. 线程池的底层实现原理是什么? 11. 线程池队列满了,任务丢失吗? 12. 线程池的拒绝策略类型有哪些? 13. 线程池如何合理配置参数? 这些问题涵盖了线程池的基本概念、使用场景、实现原理以及相关的配置和策略等方面的知识。了解这些问题能够帮助面试者更好地理解和应用Java线程池。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java线程池面试题有哪些?java线程池常见面试题](https://blog.csdn.net/muli525/article/details/123553744)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [(一)【Java精选面试题】线程池底层实现原理(含答案)](https://blog.csdn.net/qq_30999361/article/details/124924343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值