Das waitFor-Deadlock-Problem des Prozesses und seine Lösung

1. Das Problem tritt erneut auf

Verwenden Sie das Plug-In wkhtmltopdf, um HTML in PDF zu konvertieren und Barcode-Etiketten zu drucken.

Es ist kein Problem, zwei oder drei Blätter zu drucken. Wenn das Programm zu viele gedruckt wird, tritt diese Situation auf. Nach einer langen Zeit reagiert das Programm nicht oder meldet keinen Fehler, und im Hintergrund wird kein Programm ausgegeben. Dies ist nach mehreren Versuchen der Fall. Ich habe das Gefühl, dass das Programm gewartet hat, daher vermute ich einen Deadlock.

 

Zweitens finden Sie den Grund

Nachdem ich nach Informationen gesucht hatte, erfuhr ich: Process.waitFor kann einen Deadlock verursachen?

Da das lokale System einen begrenzten Pufferpool für die Standardeingabe und -ausgabe bereitstellt, kann ein falsches Schreiben in die Standardausgabe und das Lesen von der Standardeingabe zu einem Deadlock des Unterprozesses führen. Der Schlüssel zum Problem liegt im Pufferbereich: Die Standardausgabe ausführbarer Programme ist relativ groß, aber der Standardpuffer des laufenden Fensters ist nicht groß genug, sodass Blockierungen auftreten. Analysieren Sie als Nächstes den Puffer. Wenn das Runtime-Objekt exec (cmd) aufruft, startet die JVM einen untergeordneten Prozess, der drei Pipeline-Verbindungen mit dem JVM-Prozess herstellt: Standardeingabe, Standardausgabe und Standardfehlerstrom. Angenommen, das Programm schreibt ständig Daten in den Standardausgabestream und den Standardfehlerstrom und die JVM liest sie nicht, kann es nicht weiter Daten schreiben, wenn der Puffer voll ist, was schließlich zu einer Blockierung von waitfor () führt.

Es sollte beachtet werden, dass sowohl stdout als auch stderr des Leseprogramms Blockierungsvorgänge sind, was bedeutet, dass sie getrennt in zwei Threads gelesen werden müssen, anstatt gleichzeitig in einem Thread zu lesen, da sonst möglicherweise immer noch Blockierungen auftreten: z. B. zuerst lesen Nehmen Sie stdout und lesen Sie dann stderr. Wenn die stderr-Ausgabe des Programms den Puffer gefüllt hat, blockiert das Programm die Ausführung und setzt sie nicht fort, aber der Java-Thread wird beim Lesen von stdout blockiert, und stderr wird nur gelesen, wenn stdout beendet ist. Das Ergebnis ist, dass das Programm für Sie feststeckt, während Sie aufeinander warten

1. Wenn Sie Runtime.exec zum Ausführen eines Befehls verwenden, erstellt der JAVA-Thread einen untergeordneten Prozess zum Ausführen des Befehls, und der untergeordnete Prozess und der JAVA-Thread werden unabhängig voneinander ausgeführt.

2. Der JAVA-Thread muss warten, bis die Ausführung des Befehls abgeschlossen ist, und den Protokoll- und Rückgabewert des Befehls verarbeiten . Daher rufen wir Process.waitFor im JAVA-Thread auf, um aufzulegen und auf den Abschluss des untergeordneten Prozesses zu warten.

3. Wenn der untergeordnete Prozess ausgeführt wird, werden die Protokollinformationen kontinuierlich gedruckt, und wir erhalten das normale Ausgabeprotokoll und das Fehlerprotokoll zur Verarbeitung über Process.getInputStream und Process.getErrorStream .

4. Zu diesem Zeitpunkt schreibt der untergeordnete Prozess weiterhin Daten in den JAVA-Thread, und der JAVA-Thread wurde nach dem Aufruf von Process.waitFor blockiert und angehalten , und der untergeordnete Prozess schreibt ständig Daten in den JAVA-Thread, wenn unser Process.getInputStream- Puffer Der Puffer ist voll und der JAVA-Thread wird weiterhin angehalten, ohne die Daten im Puffer zu verbrauchen. Infolgedessen kann der untergeordnete Prozess keine Daten mehr in den Puffer schreiben, und der untergeordnete Prozess hängt ebenfalls. 5. Zu diesem Zeitpunkt befinden sich der JAVA-Thread und der untergeordnete Prozess in einem angehaltenen Zustand. Der JAVA-Thread wartet auf das Ende des untergeordneten Prozesses und der untergeordnete Prozess wartet darauf, dass der JAVA-Thread die Daten im Pufferpuffer verbraucht. Die beiden warten aufeinander und verursachen einen Deadlock .

Deadlock-Schema

Drei, die Lösung

1. Ideen

Da der untergeordnete Prozess hängt, weil die Daten im Pufferpuffer nicht verbraucht werden, beginnen wir hier.

a. Verbrauchen Sie die Daten im Puffer

b. Wenn der JAVA-Thread Process.waitFor aufruft , wird der Thread angehalten, und wir verwenden mehrere Threads, um Daten zu verbrauchen.

2. Normales Flussdiagramm

3. Code-Implementierung

Alter Code, der zum Deadlock neigt

public static boolean execCommond(String... args) {
    boolean flg = true;
    Runtime run = Runtime.getRuntime();
    try {
        Process p;
        if (args != null && args.length == 1) {
            p = run.exec(args[0]);
        } else {
            p = run.exec(args);
        }
        LoggerUtils.info(CmdExecUtils.class, p.getInputStream() + "....getInputStream..");
        BufferedInputStream in = new BufferedInputStream(p.getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        
        LoggerUtils.info(CmdExecUtils.class, inBr + "....inBr..");
        
        String lineStr;
        while ((lineStr = inBr.readLine()) != null) {
           LoggerUtils.info(CmdExecUtils.class, lineStr );
            System.out.println(lineStr);// 打印输出信息
        }
        if (p.waitFor() != 0) {
            if (p.exitValue() == 1) {
               logger.info("==================================命令执行失败!");
                flg = false;
            }
        }
        inBr.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
        flg = false;
    }
    return flg;
}

Der Code nach dem Lösen des Deadlocks:

private static ThreadPoolExecutor executor;

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

/**
 * 流处理
 * @param stream
 */
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) {
                    LoggerUtils.debug(CmdExecUtils.class,line);
                }
            } catch (IOException e) {
                LoggerUtils.error(CmdExecUtils.class,"exec error : {}", e);
            }
        }
    });
}

public static boolean execCommond(String... args) {
    boolean flg = true;
    Runtime run = Runtime.getRuntime();
    try {
        Process p;
        if (args != null && args.length == 1) {
            p = run.exec(args[0]);
        } else {
            p = run.exec(args);
        }

        InputStream stream=p.getInputStream();
        LoggerUtils.info(CmdExecUtils.class, stream + "....getInputStream..");

        //消费正常日志
        clearStream(stream);
        //消费错误日志
        clearStream(p.getErrorStream());

        if (p.waitFor() != 0) {
            if (p.exitValue() == 1) {
                LoggerUtils.info(CmdExecUtils.class,"=============exec=====================命令执行失败!");
                flg = false;
            }
        }

    } catch (Exception e) {
        e.printStackTrace();
        flg = false;
    }
    return flg;
}

Ich denke du magst

Origin blog.csdn.net/qq_34680444/article/details/109642653
Empfohlen
Rangfolge