线程池优雅关闭的哲学

引言

关于并发的哲学,本文将着重强调那些关于线程池优雅关闭的一些技巧,希望对你有所启发。

强制关闭线程池的弊端

对于池化的线程池,如果采用强制关闭的方式将线程池直接关闭,就可能存在上下文消息消息,无法的很好的做到启动时继续从上次进度直接运行。

所以,一般来说使用线程池的关闭,我们一般建议是通过如下两个线程池内置的API采用协作式的方式将打断标识设置为true,让线程按照不同的下面两个方法不同的语义完成打断工作:

java 复制代码
 //等待所有任务结束后关闭
        threadPool.shutdown();
        //即刻关闭所有的任务,返回已提交但是还未开始的任务
        threadPool.shutdownNow();

阻塞式操作的多线程池中断问题

对于shutdown方法而言,他要求线程完成手里的任务后关闭,这就可能出现阻塞式操作不可中断的情况,我们以一个多生产者单消费者为例来说明这个问题。

假设我们现在有下面这样的多个生产者,一旦队列空间被打满,及时我们可以采用线程的打断操作将这个任务的线程打断,我们也无法很好的处理当前要提交的元素,从而导致元素丢失:

java 复制代码
private final BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue, Thread thread) {
        this.queue = queue;

    }

    @Override
    public void run() {
        while (true) {
            try {
                //任务遇到有界队列被阻塞住,直接打断会导致本次的元素信息丢失
                queue.put(String.valueOf(System.currentTimeMillis()));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

基于标识的上下文中断

对应我们给出的解决策略可以是在任务中添加一个成员域将中断后未及时处理的成员保存下来,让线程进行后续清理,最后再将中断状态往上层代码栈传递:

java 复制代码
private String element;
    
    @Override
    public void run() {
        boolean interrupted = false;
        try {
            while (!interrupted) {
                String elem = String.valueOf(System.currentTimeMillis());
                try {
                    //任务遇到有界队列被阻塞住,直接打断会导致本次的元素信息丢失
                    queue.put(elem);
                } catch (InterruptedException e) {
                    interrupted = true;
                    element = elem;
                }
            }
        } finally {
            Thread.currentThread().interrupt();
        }

    }

基于原子化标识优雅关闭

还有一种基于原子化标识的操作,通过利用阻塞队列当前可用长度提前感知而避免阻塞,由此当线程需要中断时,就能够及时的响应并完成中断:

java 复制代码
public void run() {
        boolean interrupted = false;

        try {
            while (!interrupted) {
                String item = RandomUtil.randomString(10);

                synchronized (queue) {
                    int remainingCapacity = queue.remainingCapacity();
                    if (--remainingCapacity >= 0) {
                        try {
                            queue.put(item);
                        } catch (InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
            }
        } finally {
            Thread.currentThread().interrupt();
        }


    }

同理消费者也是通过原子操作来取元素这里就不多赘述了:

java 复制代码
@Override
    public void run() {

        boolean interrupted = false;

        try {
            while (!interrupted) {
                synchronized (queue) {
                    int remainingCapacity = queue.remainingCapacity();
                    if (--remainingCapacity == 0) {
                        try {
                            queue.take();
                        } catch (InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
            }
        } finally {
            Thread.currentThread().interrupt();
        }

    }

利用毒丸优雅关闭

还有一种做法也是比较常见的做法,即毒丸,当需要中断线程时用一个协定的标识存入队列中:

java 复制代码
 @Override
    public void run() {
        boolean interrupted = false;

        try {
            while (!interrupted) {
                String item = RandomUtil.randomString(10);

                synchronized (queue) {
                    int remainingCapacity = queue.remainingCapacity();
                    if (--remainingCapacity >= 0) {
                        try {
                            queue.put(item);
                        } catch (InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
            }
        } finally {
            synchronized (queue) {
                int remainingCapacity = queue.remainingCapacity();
                if (--remainingCapacity >= 0) {
                    try {
                        queue.put(endElement);
                    } catch (InterruptedException e) {
                        
                    }
                }
            }
            Thread.currentThread().interrupt();
        }


    }

同理消费者的也是基于毒丸标识感知异常做到优雅中断:

java 复制代码
@Override
    public void run() {

        boolean interrupted = false;

        try {
            while (!interrupted) {
                synchronized (queue) {
                    int remainingCapacity = queue.remainingCapacity();
                    if (--remainingCapacity == 0) {
                        try {
                            String element = queue.take();
                            if (endElement.equals(element)) {
                                break;
                            }
                        } catch (InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
            }
        } finally {
            Thread.currentThread().interrupt();
        }

    }

利用中断状态优化shutdownNow的线程池状态管理

从微观的角度了解了关于线程池中的线程的优雅关闭几种技巧之后,我们再来聊聊线程池维度对于任务的把控。

上文我们了解到shutdown是优雅关闭,而shutdown是近实时且能够返回已提交但是未开始的任务,所以在进行任务关闭时,如果我们希望知晓任务处理进度以及近视时的将所有任务关闭,那么我们就应该使用shutdownNow,获取所有的已提交但是未取消的任务,由此我们就可以获得通过互斥法获得运行时但是被中断的任务了:

java 复制代码
List<Runnable> runnableList = threadPool.shutdownNow();
        try {
            threadPool.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        

小结

参考

相关推荐
中国lanwp3 分钟前
Spring 全局异常处理机制:多个 @ControllerAdvice 与重复 @ExceptionHandler
java·数据库·spring
Ashlee_code5 分钟前
关税战火中的技术方舟:新西兰证券交易所的破局之道 ——从15%关税冲击到跨塔斯曼结算联盟,解码下一代交易基础设施
java·python·算法·金融·架构·系统架构·区块链
hqxstudying2 小时前
SpringBoot启动项目详解
java·spring boot·后端
你我约定有三2 小时前
分布式微服务--Nacos作为配置中心(补)关于bosststrap.yml与@RefreshScope
java·分布式·spring cloud·微服务·架构
keepDXRcuriosity3 小时前
IDEA识别lombok注解问题
java·ide·intellij-idea
酷飞飞3 小时前
C语言的复合类型、内存管理、综合案例
java·c语言·前端
宸津-代码粉碎机4 小时前
LLM 模型部署难题的技术突破:从轻量化到分布式推理的全栈解决方案
java·大数据·人工智能·分布式·python
都叫我大帅哥4 小时前
TOGAF实战解码:六大行业案例解析与成功启示
java
都叫我大帅哥4 小时前
RabbitMQ消息确认机制:从外卖小哥到数据安全的奇幻漂流
java·rabbitmq
周航宇JoeZhou7 小时前
JP3-3-MyClub后台后端(二)
java·mysql·vue·ssm·springboot·项目·myclub