Java线程池阻塞问题场景分析

背景:

        每10s会向线程池(此业务专用)写入一个任务,任务内容是:使用CMD命令行从显卡驱动来获取GPU信息

问题:

        有时候会碰到显卡异常的情况,这种就获取不到,线程池目前用了10个线程,有时候卡住获取比较慢,就会导致线程一直不释放,导致线程池提交任务异常

问:

        阻塞的线程 如何释放,此场景如何优化

当时有问小伙伴此方式有没有提供超时参数相关,小伙伴当时没回复,今日回复有测过 Process.waitFor(),

但是没效果(o(╥﹏╥)o当时没看清是通过CMD的,误以为是显卡提供的接口,导致后面的排查方向错了,但是也误打误撞走进另外一条路,有了新的收获)

先放一个思路

        采用Process.waitFor【等价FuturnTask.get】指定参数来设置最大超时等待时间(实际使用需考虑死锁问题---主进程在等待,子进程写满缓冲区,如果非要使用waitFor,那就在waitFor之前单独启2个额外的线程,分别用于处理标准输入流 和 标准错误流)

给一段伪代码:

    String cmd = "路径\\test.bat";
    //echo sleep start
    //ping 127.0.0.1 -n 5 > nul
    //echo sleep end
    try {
    Process process = Runtime.getRuntime().exec(cmd);
    process.waitFor(6, TimeUnit.SECONDS);//有需要可处理死锁问题,没有则不需要
    
    process.exitValue();
    // ||  两行二选一执行--先埋坑放这里
    try{
        process.exitValue();
    } catch (Exception e){
        e.printStackTrace();
    }
    
    InputStream is = process.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    String content = br.readLine();
    while (content != null) {
        System.out.println(content);
        content = br.readLine();
    }
    } catch (IOException | InterruptedException e) {
    e.printStackTrace();
    }

这里的解题思路其实是指定任务执行的超时时间,其他场景:rpc中的连接超时、读超时时间、尝试获取锁的最大等待时间等等的配置和这都是一个意思(如果一个任务有较多的子流程,可以挨个控制每个子流程的最大执行时间,从而让整个任务的最大执行时间可控)

下面继续聊当时考虑的另外一个方向以后的踩坑之路(因认为没有最大超时时间的配置 故当时的思路就到另外的方向了)

        小伙伴一开始的提问是:CompletableFuture的cancel方法能中断线程的执行吗

        已知:我们不能直接使用thread.stop()这种非常强硬的手段来直接停止线程,风险很大,故提出了 中断interrupt(这里interrupt 和 stop的区别需要了解一下:java并发编程--- stop() 和 interrupt() 方法的主要区别_停止线程stop和interrupt的区别-CSDN博客)

        但是中断 能直接让线程停止执行,从而去阻塞队列中重新获取任务吗?

        了解 stop 和 interrupt的小伙伴就知道了,中断仅是给线程一个标记,且线程完全可以不搭理这个标记依旧“我行我素”,那么上面的问题答案就出来了,没办法去中断一个任务的执行,除非线程任务有做相应的处理。

        当线程处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用此线程的 interrupt() 方法,会使该线程返回到 RUNNABLE 状态,同时该线程的代码会触发 InterruptedException 异常

这里给一个demo:

public static Object longRunningMethod(Long time) {
    try {
        System.out.println("sleep start");
        thread = Thread.currentThread();
        Thread.sleep(time);
        System.out.println("sleep end");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("目前状态:" + Thread.currentThread().getState());
    return "完成";
    }

    public static void main(String[] args) throws InterruptedException {
    // 创建一个新的CompletableFuture
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        // 这里是要执行的方法
        return longRunningMethod(100L);
    });
    Thread.sleep(10);
    System.out.println(thread.getState());
    thread.interrupt();

    Thread.sleep(100000L);
} 
  

后来我们采取的优化方式:

线程池给获取GPU任务独享,任务10s一次,这个时候利用 定时任务的思想就不要直接使用cron方式了,使用fixedDelay思想(@Scheduled中fixedDelay、fixedRate、initialDelay 和cron表达式的解析及区别_@scheduled initialdelay-CSDN博客)

最后一次执行完时间 - > 下次开始时间

因为现在阻塞了就不发请求去获取就是最好的,如果GPU异常恢复了就会响应之前的请求,如果GPU异常没恢复,多发几个请求也基本上没用,线程资源因为阻塞也没办法释放

最终采用此方式:根据上次命令执行完毕时间 去推导 下次任务执行时间来达到优化此业务的目的

结语:

        之所以说上面使用Process.waitFor() 或 FuturnTask.waitFor不全符合现有场景,那是因为他们都是基于主线程(创建任务的线程)自身去阻塞然后唤醒以后检查 任务是否执行完

但是!任务该阻塞中依旧会在阻塞(也别想拉出对应线程去中断,麻烦且不优雅[做确实可以做~~~]),即使主线程给放开了,但是子线程依旧在阻塞,数量一多线程池又被干光了

一周后在《深入理解Java虚拟机》书中无意间看到一个优化案例,豁然开朗

最终优化方案:

        脚本里写个循环,获取到结果以后sleep 5秒然后再获取。而不用向现在这样在程序内间隔5秒去执行命令,避免fork(如果能找到java api也未尝不可)

你可能感兴趣的:(java,开发语言)