Java执行Linux命令死锁阻塞挂起,Runtime.getRuntime().exec阻塞卡死问题解决

news2024/11/16 9:54:11

1、前言:
最近在做一个需求需要调用linux下的ffmpeg来对处理视频,很简单的需求,我像往常一样写下如下的代码片段:
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
But当我运行代码时,发现代码执行到waitFor后阻塞住了,我以为这也许是ffmpeg处理视频需要时间,毕竟是同步阻塞的方法,那我就等一会吧!
一分钟……两分钟……10分钟……
我渐渐的意识到,这TM不对劲啊!
于是我通过以下命令查询了下linux执行情况。
ps -aux 
输出的结果显示该脚本当前还在运行,而脚本的日志并没有从inputstream进行输出。我渐渐的慌了!因为我在linux环境下直接执行ffmpeg -i xxx,是没有问题的。

2 问题分析
2.1 观看JDK文档

根据查看JDK的文档,我看到Process.waitFor可能导致死锁?!仔细观看JDK的文档说明,发现文档上已经对死锁的情况进行了分析。
如果InputStream使用的buffer缓冲区有限的话,可能会导致阻塞和死锁 。
2.2 死锁原因

经过查阅资料,发现

  1. 当我们使用Runtime.exec执行命令时,JAVA的线程会创建一个子进程,用于执行命令,而且子进程和JAVA线程会分别独立运行。
  2. JAVA线程需要等待命令的执行完成,对命令的日志和返回值进行处理,所以我们在JAVA线程中调用Process.waitFor挂起来等待子进程完成。
  3. 子进程执行时,不断的打印日志信息(ffmpeg命令会输出大量执行日志,这是导致挂起的直接原因),我们通过Process.getInputStream和Process.getErrorStream进行获取正常输出日志和错误日志进行处理。
  4. 这个时候子进程不断的向JAVA线程写入数据,而JAVA线程调用Process.waitFor后已经阻塞挂起,而子进程在不断的向JAVA线程进行写入数据,当我们的Process.getInputStream的buffer缓冲区被写满,而JAVA线程依然挂起并未消费buffer中的数据,导致子进程无法继续向buffer缓冲区中继续写入数据,导致子进程也挂起。
  5. 这个时候JAVA线程和子进程都处于挂起的状态,JAVA线程等待子进程的结束,子进程等待JAVA线程对buffer缓冲区中的数据进行消费。两者在相互等待导致死锁。
    2.2.1 死锁原理图

3 解决方案
3.1 解决方案的思路

既然是由于buffer缓冲区的数据没有消费导致子进程挂起,那么我们从这里下手。1. 消费buffer缓冲区中的数据 2. 当JAVA线程调用Process.waitFor后,线程会进行挂起,那我们就使用多线程进行消费数据。
3.2 正常流程图

3.3 代码实现

下面写了一个代码实现的demo,我是在linux环境调用ffmpeg处理视频的,所以例子为执行ffmpeg命令,你可以根据自己的实际情况来重写。

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CmdUtils {
    private static final Logger logger = LoggerFactory.getLogger(CmdUtils.class);
    private static ThreadPoolExecutor executor;

    static {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("cmd-pool-%d").build();
        //根据实际情况创建线程池
        executor = new ThreadPoolExecutor(10,
                20,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(1024),
                namedThreadFactory,
                new ThreadPoolExecutor.AbortPolicy());
    }

    public static boolean exec(String cmd) throws IOException, InterruptedException {
        String[] cmds = {"/bin/sh", "-c", cmd};
        logger.info("exe cmd {}", cmd);
        Process process = Runtime.getRuntime().exec(cmds);

        //消费正常日志
        clearStream(process.getInputStream());
        //消费错误日志
        clearStream(process.getErrorStream());

        //i为返回值,判断是否执行成功
        int i = process.waitFor();
        if (i != 0) {
            logger.error("Failed to call shell command and the return status's is: {}", i);
            return false;
        }
        return true;
    }

    private static void clearStream(InputStream stream) {
        //处理buffer的线程
        executor.execute(new Runnable() {
            @Override
            public void run() {
                String line = null;
                try (BufferedReader in = new BufferedReader(new InputStreamReader(stream));) {
                    while ((line = in.readLine()) != null) {
                        logger.debug(line);
                    }
                } catch (IOException e) {
                    logger.error("exec error : {}", e);
                }
            }
        });
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        long start = System.currentTimeMillis();
        String cmd = String.format("ffmpeg -i %s -vf \"scale=1080:640,pad=1080:1920:0:640:black\" %s", "/input.mp4", "/out.mp4");
        boolean cmdResult = CmdUtils.exec(cmd);
        logger.info("exec cmd = {} out = {}, cost = {}", cmd, cmdResult, (System.currentTimeMillis() - start));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端笔记 ---- document.execCommand 函数整理

1. 语法 使用语法 bool document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)返回值 一个 Boolean &#xff0c;如果是 false 则表示操作不被支持或未被启用。 备注&#xff1a; 在调用一个命令前&#xff0c;不要尝试使用返回值去校验浏览器的兼容性 2. 参…

基于Vue和SpringBoot的宾馆管理系统的设计和实现

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

树形结构——红黑树

前言 在 JDK1.8 之后&#xff0c;HashMap 的底层是由数组、链表、红黑树来实现的&#xff0c;当数组长度到 64 的时候&#xff0c;或者链表长度到 8 的时候&#xff0c;会调用 treeifyBin 转换为红黑树实现。因为红黑树是小伙伴们面试的时候经常被考到的知识点&#xff0c;因此…

OSPF-MGRE实验

1.首先配ip [r6]int g 0/0/1 [r6-GigabitEthernet0/0/1]ip add 192.168.1.2 24 [r6-GigabitEthernet0/0/1]int g 0/0/0 [r6-GigabitEthernet0/0/0]ip add 192.168.2.2 24 [r6-GigabitEthernet0/0/0]int g 0/0/2 [r6-GigabitEthernet0/0/2]ip add 192.168.3.2 24 [r6-GigabitEt…

git pull 和git fetch

1.git fetch 用户一&#xff1a;本地初始化项目&#xff0c;创建文件&#xff0c;保存本地仓库&#xff0c;提交远程仓库 $ git init $ touch file.txt $ git add . $ git commit -m "创建了file.txt文件" [master (root-commit) 4dcee36] 创建了file.txt文件1 file …

简单又好用的财务分析工具有哪些?

什么样的财务分析工具才能算是简单又好用&#xff1f;是能够快速完成组合多变的财务指标运算分析&#xff1b;能够充分发挥企业经营健康晴雨表作用&#xff0c;反映企业财务健康状态&#xff1b;还是能够支持多维度动态分析、自助分析&#xff1b;或者是轻松合并账套&#xff0…

跨域与JSONP

1、同源策略 1.1、什么是同源 如果两个页面的协议&#xff0c;域名和端口都相同&#xff0c;则两个页面具有相同的源。 例如&#xff0c;下表给出了相对于 http://www.test.com/index.html 页面的同源检测&#xff1a; URL 是否同源 原因 http://www.test.com/other.html…

智慧图书馆中的“智慧”体现在哪些方面?

在信息时代背景下&#xff0c;各个领域都发生了巨大变革&#xff0c;图书馆也不例外&#xff0c;开始逐步向着现代化方向发展。传统图书馆存在较多的缺陷&#xff0c;已经无法满足人们的借阅需求&#xff0c;引进信息化技术&#xff0c;打造智慧图书馆是目前图书馆的必然发展趋…

Linux学习记录——오 vim基本知识

** Linux开发工具 ** Linux开发工具——vim vim最小集 vim是一个多模式编辑器&#xff0c;vi也一样&#xff0c;但vim兼容了vi的所有指令&#xff0c;还有一些独有的特性&#xff0c;本篇只针对vim展开。vim有各种模式&#xff0c;每个模式的用法都有差别&#xff0c;模式…

【数据在内存中的存储】肝货满满

前言 我们知道在C语言中的基本内置类型&#xff1a; char //字符数据类型short //短整型int //整形long //长整形long long //更长的整形float //单精度浮点型double //双精度浮点型 那么这些类型是如何存储的呢&#xff1f; 回顾指针类型&#xff1a; *int pi*char pc*float…

String的讲解(Java系列9)

目录 前言&#xff1a; 1.String 1.1字符串的构造 1.2Sting对象的比较 1.3字符串的查找 1.4字符串的转化 1.4.1数值和字符转换 1.4.2大小写转换 1.4.3字符串转数组 1.4.4格式化 1.5字符串的替换 1.6字符串拆分 1.7字符串截取 1.8字符串去空格 1.9字符串的不可变…

Matplotlib笔记 · 绘图区域的结构和子图布局与划分(figure, axes, subplots)

文章目录1. 绘图区域的结构2. subplot系方法 ( subplot布局 )2.1 使用 add_subplot(nrows, ncols, index) 逐一创建子图2.2 控制子图大小和位置 ( add_subplot(nrows, ncols, index) 参数详解 )2.3 使用 subplots(nrows, ncols) 批量创建多张子图3. axes系方法 ( axes布局 )3.1…

基于张量变换域低秩正则化的图像恢复方法

高光谱图像、磁共振图像、RGB图像等都可以表示成三维数组的形式&#xff0c;在数学上将这种多维数组称为高阶张量&#xff0c;同样&#xff0c;上述三种图像都可以表示成三阶张量。在空间上&#xff0c;图像本身就具有结构相似性&#xff0c;在高光谱图像的第三个模态上&#x…

日志分析工具

iis、windows日志做日志分析比较麻烦&#xff0c;这里找到了一款好用的免费的日志分析工具 Log Parser Lizard&#xff0c;下载这个工具之前建议先安装LogParser虽然他会自动弹窗提示。 1. 安装软件 安装没什么好说的一直下一步下一步就行 启动之后点击OK 弹出激活页面让激活…

mod函数怎么取模

mod 是 MySQL 中的数值函数&#xff0c;写法为&#xff1a;mod(x,y)&#xff0c;意思是返回x/y的取模的值。 什么是取模&#xff1f;取模就是取余数。 ① 如果第一个值比第二个值大&#xff0c;我整理出来的取模公式就是&#xff1a;第一个值-第一个值里面包含了几个第二个值相…

MATLAB-多边形填充图绘制

fill函数用于绘制并填充二维多边图形。将数据点视为多边形顶点&#xff0c;并将此多边形涂上颜色&#xff0c;便于用户理解图形中的数据代表的含义。具体调用方法如下:fill(X, Y,C):用X和Y中的数据生成多边形&#xff0c;用C指定颜色填充。其中C为色图向量或矩阵。若C是行向量&…

商标注册流程有什么步骤

​一、商标注册流程有什么步骤? 商标注册流程&#xff1a; 1、需要企业提供营业执照副本复印件和商标样稿及主要商品或服务&#xff0c;递交商标局; ​ 2、商标局形式审查(7-15个工作日)接到《商标注册申请受理通知书》; 3、商标局实质审查(5-8个月左右); 4、商标公告(3…

【Linux多线程编程】3. 多线程共享资源

回顾 上篇文章【Linux多线程编程】2.线程创建与回收 简单介绍了如何创建一个线程并且回收它&#xff0c;末尾给出了如下这段代码&#xff0c;本文将从这段代码入手介绍线程资源、线程共享资源、线程独占资源&#xff0c;并在最后引出多线程安全访问资源的方法。 /** test_pth…

新华三(H3C)的沉浮往事

根据2023年1月3日紫光股份发布的最新公告&#xff0c;Hewlett Packard Enterprise Company全资子公司H3C Holdings Limited&#xff08;“HPE 开曼”&#xff09;和Izar Holding Co&#xff0c;将向紫光股份全资子公司紫光国际信息技术有限公司出售其持有的新华三集团有限公司合…

【Linux】伪目标 PHONY | 探讨项目构建问题 | Makefile | 依赖关系与依赖方法

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》&#x1f448; 猛戳订阅 &#x1f525; &#x1f4ad; 写在前面&#xff1a;本章我们要学习的是 makefile。会不会写 makefile&#xff0c;从一个侧面说明一个人是否具备完成大型工程的能力。一个工程中的源文件不计…