首页 > 其他分享 >线程饥饿锁

线程饥饿锁

时间:2023-09-18 16:44:24浏览次数:28  
标签:get 相互依赖 任务 饥饿 Future CompletableFuture 线程

故障描述

为提高系统吞吐量,优化接口的响应速度,让页面响应时间更短,将某个聚合接口的多个串行调用更改为异步并行的方式

上线后,不到一会出现大量的线程池资源耗尽的异常告警,异常日志

Exception in thread "main" java.util.concurrent.ExecutionException: 
java.util.concurrent.RejectedExecutionException: 
Task java.util.concurrent.FutureTask@42936575
[Not completed, task = xxxxx] rejected from 
java.util.concurrent.ThreadPoolExecutor@33f18ac
[Running, pool size = X, active threads = X, queued tasks = N,
 completed tasks = M]

大量任务被拒绝,原因是线程池和队列均已被耗尽,看到该异常第一反应是线程池的最大线程数和队列设置太小,但是仔细分析发现业务代码写的有问题,线程池提交的任务存在相互依赖

在大小有限的线程池中,执行有相互依赖的任务,可能产生死锁

故障代码

为复现问题,定义一个有限大小的线程池,线程池大小:2,队列大小:1,拒绝策略:AbortPolicy


private static final ExecutorService poolExecutor = new ThreadPoolExecutor(2, 2,
            0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(),
            new ThreadPoolExecutor.AbortPolicy() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                    log.error("rejectedExecution");
                    super.rejectedExecution(r, e);
                }
            }
    );

模拟有问题的业务代码,任务有依赖且等待执行结果:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    Future<Object> futureA = poolExecutor.submit(() -> {
        Future<Object> future = poolExecutor.submit(() -> null);
        return future.get();
    });
    Future<Object> futureB = poolExecutor.submit(() -> {
        Future<Object> future = poolExecutor.submit(() -> null);
        return future.get();
    });

    futureA.get();
    futureB.get();

    poolExecutor.shutdown();
}

以上代码的执行顺序如下所示

image-20230918162143249

提交到线程池的任务如果相互依赖,多个任务也被同一线程池调度执行,A任务在等待B任务完成的同时,占用的线程不会结束,如果流量足够,线程池里的线程都被A任务占用完而不会结束,那么在任务队列的B任务永远不会有线程去执行,从而出现了线程饥饿锁的出现。

如何避免

  1. 设置更大的线程池大小或者选择不受限制的线程池

    虽然更大的线程池能够减少或者避免线程饥饿锁的出现,但是线程资源是宝贵的,不可能无限创建,该方法有些不合理

  2. 使用java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)

    使用带超时时间的Future.get虽然能够让后续的任务尽快返回,不阻塞接口,但是后续请求的功能是非正常的,这种方式明显不合理

  3. 使用线程池拒绝策略为:CallerRunsPolicy

    将拒绝策略更改为CallerRunsPolicy,因为线程池的大小以及流量无法确定,那么在线程异常时将异步执行退化为串行执行,也不失为一种避免线上故障的方法,但是在饥饿锁的场景下会让web容器的线程也受到影响

  4. 使用不同的线程池隔离有互相依赖的任务

    有相互依赖的任务,隔离到不同的线程池中执行,使得相互之间不再竞争使用相同的线程池资源,理论上的好方法,但是在实际业务中,我们经常无法区分出哪些业务应该归拢到一个线程池中,哪些应该分开创建线程池,而且线程资源宝贵,仅仅是因为相互依赖的任务就创建不同的线程池也不是一个好的方式

  5. 使用CompletableFuture + 自定义线程池来编排存在相互依赖的任务

public static void main(String[] args)  {

    CompletableFuture<Void> futureA = CompletableFuture.supplyAsync(() -> null)
        .thenComposeAsync(result1 -> CompletableFuture.supplyAsync(() -> null));

    CompletableFuture<Void> futureB = CompletableFuture.supplyAsync(() -> null)
        .thenComposeAsync(result2 -> CompletableFuture.supplyAsync(() -> null));

    // 等待所有的任务完成
    CompletableFuture.allOf(futureA, futureB).join();
}

使用 CompletableFuture 可以更清晰地表达任务之间的依赖关系,而不需要显式地操作线程池和 Future,更方便地编排存在相互依赖的任务,避免线程饥饿问题,并提供更灵活和强大的异步编程能力。

标签:get,相互依赖,任务,饥饿,Future,CompletableFuture,线程
From: https://www.cnblogs.com/wrxiang/p/17712361.html

相关文章

  • python多线程中锁的概念 threading.Lock
    https://blog.csdn.net/qq_21439971/article/details/79356248 python的锁可以独立提取出来12345678mutex  =  threading.Lock()#锁的使用#创建锁mutex  =  threading.Lock()#锁定mutex.acquire([timeout])#释放mutex.release()......
  • 深入理解操作系统中进程与线程的区别及切换机制(上)
    进程所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程!一个进程可以包含一个或者多个线程,但对于CPU来说他就是一个任务而已;在早......
  • 深入理解操作系统中进程与线程的区别及切换机制(下)
    前言上一篇文章中我们了解了进程的执行方式,包括早期单核处理器上的顺序执行以及引入多任务概念实现的伪并行。我们还探讨了进程的状态模型。进程可以处于就绪、运行、阻塞和结束等不同的状态。在本篇文章中,我将探讨研究进程的状态模型、控制结构和切换机制。希望通过这篇文章的分......
  • 线程同步与进程同步方式
    要注意这里的同步并不是指同时进行的意思,而是按照先后顺序依次进行。首先了解一下同步与互斥的概念:同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系;互斥:多个进程在同一时刻只有一个进程能进入临界区。一、进程同步方式进程同步就是控制多个进程按一......
  • python多线程
    Python多线程参考文章:python多线程详解(超详细)、Python线程池(threadpool)创建及使用+实例代码、第二十章多线程1、多线程的概念2、python多线程的基本使用方法3、多线程的优点及与多进程的关系1、多线程的概念线程也叫轻量级进程,是操作系统能够进行运算调度......
  • 利用SharedArrayBuffer进行多线程编程
    利用SharedArrayBuffer进行多线程编程在现代Web应用程序中,性能是一个至关重要的因素。为了提高Web应用程序的性能,我们经常需要执行并行计算,例如图像处理、音频处理或数据分析。在这种情况下,多线程编程是一种强大的工具,它允许我们充分利用多核处理器。然而,多线程编程并不是一件容易......
  • .NET中测量多线程基准性能
    .NET中测量多线程基准性能 多线程基准性能是用来衡量计算机系统或应用程序在多线程环境下的执行能力和性能的度量指标。它通常用来评估系统在并行处理任务时的效率和性能。测量中通常创建多个线程并在这些线程上执行并发任务,以模拟实际应用程序的并行处理需求。在此,我们用多......
  • 深入了解信号量:多线程同步的得力工具
    随着计算机科学和软件工程的不断发展,多线程编程变得越来越重要。多线程允许程序同时执行多个任务,提高了程序的效率和性能。然而,多线程编程也引入了新的问题,例如竞态条件和数据竞争。为了解决这些问题,同步工具变得至关重要,而信号量是其中一个强大的工具。什么是信号量?信号量是一......
  • SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据
    一、背景:    利用ThreadPoolTaskExecutor多线程异步批量插入,提高百万级数据插入效率。ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。ThreadPoolTaskExecutor是ThreadPoolExecutor的封装,所以,性能更加优秀,推荐ThreadPoolTaskExecutor。二、具体细节:2.1、配置app......
  • Java多线程学习(Day01)
    目录线程简介线程实现(重点)线程状态线程同步(重点)线程通信问题进程与线程概念                                     --来自百度百科的解释:        进程(Process)是......