背景:
每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
后来我们采取的优化方式:
线程池给获取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也未尝不可)