Java がシェル org.springframework.dao.CannotAcquireLockException を呼び出すと、誤った死亡の問題 (デッドロックの問題) が発生します: 原因: com.mysql.cj.jdbc.Exceptions.MySQLTransactionRollbackException: ロック待機タイムアウトを超えました; トランザクションを再起動してください
現象: Javaを使用して関連コマンドを実行するためにシェルを呼び出すと、フリーズなどの問題が発生し、エラーが報告されます。 org.springframework.dao.CannotAcquireLockException: 原因: com.mysql.cj.jdbc.Exceptions.MySQLTransactionRollbackException: Lock待機タイムアウトを超えました; トランザクションを再起動してみてください
最初は、いつも mysql トランザクション ロックだと思っていましたが、最終的にはシェルを呼び出す Java のバグであることがわかりました
解決:
実際、主な問題は waitfor() にあります。waitFor
() の説明を見てみましょう。
JDK のヘルプ ドキュメントには、次のように書かれています。必要に応じて、Process オブジェクトによって表されるプロセスが終了するまで待ちます。子プロセスが終了した場合、このメソッドはすぐに戻ります。ただし、このメソッドを直接呼び出すと、子プロセスが終了するまで現在のスレッドがブロックされます。これはJDKのドキュメントでも説明されています。ローカルシステムは標準入出力が提供するバッファプールに対して有効であるため、標準出力への高速書き込みと標準入力からの高速読み取りを誤ると、サブプロセスがデッドロックを引き起こす可能性があります。 。さて、問題の鍵はバッファにあります。実行可能プログラムの標準出力は比較的大きいですが、実行ウィンドウの標準バッファは十分な大きさではないため、ブロッキングが発生します。次に、バッファを分析しましょう。これはどこから来たのでしょうか? ランタイム オブジェクトが exec(cmd) を呼び出すと、JVM は子プロセスを開始し、JVM プロセスとの 3 つのパイプライン接続 (標準入力、標準出力、標準) を確立します。エラーストリーム。プログラムが常に標準出力ストリームと標準エラー ストリームにデータを書き込み、JVM がそれを読み取らないと仮定すると、バッファーがいっぱいになるとデータの書き込みを続けることができなくなり、最終的に waitfor() でブロックが発生します。何が問題なのかがわかれば、それを解決できます。インターネット上で言及されている方法のほとんどは、waitfor() コマンドの前に 2 つのスレッドを開いて、ウィンドウの標準出力バッファと標準エラー ストリームの内容を読み取るというものです。
解決:
方法1
package com.xxx.xxx;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.InputStreamReader;
public class CallCmd {
// 关流方法
private void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
System.out.println("close stream failed.");
}
}
}
// 此方法用来执行脚本或命令
// String cmd:脚本(绝对路径)位置或命令内容
// File dir:执行命令的子进程的工作目录(null则表示和当前主进程工作目录相同)
public String callCMD(String cmd, File dir) {
StringBuilder result = new StringBuilder();
Process process = null;
BufferedReader bufrIn = null;
BufferedReader bufrError = null;
try {
// 执行命令, 返回一个子进程对象(命令在子进程中执行)
process = Runtime.getRuntime().exec(cmd, null, dir);
// 方法阻塞,等待命令执行完成
process.waitFor();
// 获取命令执行结果, 有两个结果: 正常的输出和错误的输出(PS: 子进程的输出就是主进程的输入)
bufrIn =
new BufferedReader(
new InputStreamReader(process.getInputStream(), "UTF-8")); // 正常的输出
bufrError =
new BufferedReader(
new InputStreamReader(process.getErrorStream(), "UTF-8")); // 错误的输出
// 读取输出
String line = null;
while ((line = bufrIn.readLine()) != null) {
result.append(line).append('\n');
}
while ((line = bufrError.readLine()) != null) {
result.append(line).append('\n');
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeStream(bufrIn);
closeStream(bufrError);
// 销毁子进程
if (process != null) {
process.destroy();
}
}
// 返回执行结果
return result.toString();
}
}
方法2
この方法でProcessBuilderを実装しています
private void execReloadAll(){
List<String> cmds = new ArrayList<String>();
## 拼装相关的脚本命令
cmds.add(StrongSwanCommand.COMMAND_SH);
cmds.add(StrongSwanCommand.COMMAND_ENV);
ProcessBuilder pb=new ProcessBuilder(cmds);
int exitCode = 0;
try {
Process process = pb.start();
/**
* 问题:调用shell时出现假死问题
* waitFor()一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。
* 对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,
* 甚至死锁。问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,哪来的这个东西,
* 当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。
* 假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。
*
*/
String result = "";
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
result = sb.toString();
exitCode = process.waitFor();
log.info("result:---{}",result);
// 等待该进程执行完毕
if (exitCode != 0) {
// 如果进程运行结果不为0,表示进程是错误退出的
// 获得进程实例的错误输出
InputStream error = process.getErrorStream();
log.info("加载失败:{}",error);
throw new BizException(ErrorCode.IKE_LOAD_ALL_ERROR.getCode(),ErrorCode.IKE_LOAD_ALL_ERROR.getMsg());
}
// 获得进程实例的标准输出
InputStream sdin = process.getInputStream();
log.info("加载:{}",sdin);
} catch (Exception e) {
log.error("执行失败", e);
throw new BizException(ErrorCode.LOAD_ALL_ERROR.getCode(),ErrorCode.LOAD_ALL_ERROR.getMsg());
}
assert exitCode == 0;
}
または実行を通じて
public static String exec(String cmd) {
String result = "";
try {
String[] shellCmd = new String[] {
"/bin/sh", "-c", cmd };
Process ps = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(
ps.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
result = sb.toString();
ps.waitFor();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}