[英]Java - Problem with multiple, concurrent runtime.exec() InputStreams
我別無選擇,只能通過對VBScript的幾個Runtime.exec()
調用來檢索一些外部數據。 我真的很討厭這種實現,因為我失去了跨平台的靈活性,但我最終可能會開發類似的* nix腳本來至少緩解這個問題。 有人問之前,我無法解決需要調用外部腳本來收集我的數據。 我會忍受導致的問題。
exec()
進程在擴展Runnable
的自定義類中運行。 它使用BufferedReader
從getInputStream()
讀入數據。
編輯 :更多的代碼按要求添加,但我不知道額外的代碼是如何相關的:)我希望它有所幫助,因為它需要一段時間來格式化! 哦,如果它的丑陋,我的代碼風格變得容易,但鼓勵建設性的批評......
public class X extends JFrame implements Runnable {
...
static final int THREADS_MAX = 4;
ExecutorService exec;
...
public static void main(String[] args) {
...
SwingUtilities.invokeLater(new X("X"));
} // End main(String[])
public X (String title) {
...
exec = Executors.newFixedThreadPool(THREADS_MAX);
...
// Create all needed instances of Y
for (int i = 0; i < objects.length; i++) {
Y[i] = new Y(i);
} // End for(i)
// Initialization moved here for easy single-thread testing
// Undesired, of course
for (int i = 0; i < objects.length; i++) {
Y[i].initialize(parent);
} // End for(i)
} // End X
class Y implements Runnable {
// Define variables/arrays used to capture data here
String computerName = "";
...
public Y(int rowIndex) {
row = rowIndex;
...
computerName = (String)JTable.getValueAt(row, 0);
...
exec.execute(this);
} // End Y(int)
public void run() {
// Initialize variables/arrays used to capture data here
...
// Initialization should be done here for proper threading
//initialize(parent);
} // End run()
public void initialize(Z obj) {
runTime = Runtime.getRuntime();
...
try {
process = runTime.exec("cscript.exe query.vbs " + computerName);
stdErr = process.getErrorStream();
stdIn = process.getInputStream();
isrErr = new InputStreamReader(stdErr);
isrIn = new InputStreamReader(stdIn);
brErr = new BufferedReader(isrErr);
brIn = new BufferedReader(isrIn);
while ((line = brIn.readLine()) != null) {
// Capture, parse, and store data here
...
} // End while
} catch (IOException e) {
System.out.println("Unable to run script");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stdErr.close();
stdIn. close();
isrErr.close();
isrIn. close();
brErr. close();
brIn. close();
} catch (IOException e) {
System.out.println("Unable to close streams.");
} // End try
} // End try
} // End initialize(Z)
...
} // End class Y
} // End class X
如果我單獨執行命令,我會按預期收集數據。 但是,如果我在類的run()
塊中執行命令(意味着調用是並發的,正如我所希望的那樣),則看起來好像只生成了一個輸入流,所有BufferedReaders
都會並發使用。
為了調試這個問題,我在控制台上輸出每個消耗的行,前面是我的類的哪個實例正在讀取輸入流。 我期待類似下面的內容,理解它們可能在實例到實例之間出現故障,但單個實例的行順序將完好無損:
exec 0: Line1
exec 1: Line1
exec 2: Line1
exec 0: Line2
exec 1: Line2
exec 2: Line2
exec 0: Line3
exec 1: Line3
exec 2: Line3
...
奇怪的是,我獲得了輸出第一行的預期實例數( Microsoft (R) Windows Script Host Version 5.7
),但在此行之后,只有一個進程繼續在輸入流中生成數據,並且所有讀者隨機使用這一個流,例如:
exec 2: Microsoft (R) Windows Script Host Version 5.7
exec 0: Microsoft (R) Windows Script Host Version 5.7
exec 1: Microsoft (R) Windows Script Host Version 5.7
exec 0: line2
exec 1: line3
exec 2: line4
...
更糟糕的是,讀者停止並且readLine()
永遠不會返回null。 我讀到這種行為可能與緩沖區大小有關,但是當我只運行兩個並發線程時,即使輸出很短,它仍然表現出相同的行為。 stdErr
沒有捕獲任何stdErr
以指示存在問題。
為了查看這是否是腳本主機的限制,我創建了一個批處理文件,該文件同時START
多個腳本實例。 我應該聲明這是在 Java 之外的cmd shell中運行的,並啟動了幾個自己的shell。 但是,每個並發實例完全返回預期結果並且表現良好。
編輯:作為另一個故障排除的想法,我決定重新啟用並發,但通過在Y.run()
塊中插入以下內容來錯開我的初始化方法:
try {
Thread.sleep((int)(Math.random() * 1200));
} catch (InterruptedException e) {
System.out.println("Can't sleep!");
} // End try
initialize(monitor);
進入我的代碼。 我開始看到前幾行的多個輸出,但很快就會回復到消費同一個生產者的多個消費者,並且一旦第一個完成的流關閉,其余的消費者就會觸發異常。 下一個使用者觸發IOException: Read error
,其余消息觸發IOException: Stream closed
!
根據maaartinus,可以運行多個並發的InputStreams
,所以現在問題變成導致不良行為的原因了嗎? 如何獨立獲取輸入流? 我不想寫一個臨時文件只是為了處理數據,如果我可以避免它。
我認為你需要注意IO變量的范圍。 這是一個非常好用的快速代碼,來自4個子進程的並發輸入流......
import java.io.*;
public class MultiExec {
private final static String[] comLines = {
"date",
"ls /var/spool/postfix",
"ls -F /usr/local/bin",
"wc -l /etc/apache2/apache2.conf"};
public void execute() {
for (int i = 0 ; i < comLines.length ; i++) {
ExecutableChild ec = new ExecutableChild (i, comLines[i]);
new Thread (ec).start();
}}
public class ExecutableChild implements Runnable {
private int prIndex;
private String executable;
public ExecutableChild (int k, String cmd) {
prIndex = k;
executable = cmd;
}
public void run () {
try {
Process child = Runtime.getRuntime().exec(executable);
BufferedReader br = new BufferedReader (new InputStreamReader (
child.getInputStream()));
for (String s = br.readLine() ; s != null ; s = br.readLine())
System.out.println ("[" + prIndex + "] " + s);
br.close();
} catch (IOException ioex) {
System.err.println ("IOException for process #"+
prIndex+ ": " + ioex.getMessage());
}}}
public static void main (String[] args) {
new MultiExec().execute();
}
}
上面代碼的輸出(%javac MultiExec.java; java MultiExec)
[2] tomcat*
[0] Thu Jan 20 18:38:31 CST 2011
[3] 368 /etc/apache2/apache2.conf
[1] active
[1] bounce
[1] corrupt
[1] defer
[1] deferred
[1] etc
[1] flush
[1] hold
[1] incoming
[1] lib
[1] maildrop
[1] pid
[1] private
[1] public
[1] saved
[1] trace
[1] usr
[1] var
如果您向我們提供了您嘗試的源代碼,我們可以進行討論。 祝福, - MS
================================================== ===========================
編輯:DN:我理解您對1行輸出的擔憂。 讓我們有一個小腳本......
#!/usr/bin/perl -w
foreach (1..50) {
print "$_\n";
}
以及上述Java代碼的編輯版本... comLines已更改,並且每個println()后添加了Thread.sleep
公共類MultiExec {
private final static String[] comLines = {
"ls /var/spool/postfix",
"perl count50.pl",
"cat MultiExec.java",
"head -40 /etc/apache2/apache2.conf"};
public void execute() {
for (int i = 0 ; i < comLines.length ; i++) {
ExecutableChild ec = new ExecutableChild (i, comLines[i]);
new Thread (ec).start();
}}
public class ExecutableChild implements Runnable {
private int prIndex;
private String executable;
public ExecutableChild (int k, String cmd) {
prIndex = k;
executable = cmd;
}
public void run () {
try {
Process child = Runtime.getRuntime().exec(executable);
BufferedReader br = new BufferedReader (new InputStreamReader (
child.getInputStream()));
for (String s = br.readLine() ; s != null ; s = br.readLine()) {
System.out.println ("[" + prIndex + "] " + s);
try {
Thread.sleep (20);
} catch (InterruptedException intex) {
}}
br.close();
} catch (IOException ioex) {
System.err.println ("IOException for process #"+
prIndex+ ": " + ioex.getMessage());
}}}
public static void main (String[] args) {
new MultiExec().execute();
}}
這是現在的輸出(在編譯/運行之后)......
[0] active
[1] 1
[2] import java.io.*;
[3] #
[2]
[0] bounce
[1] 2
[3] # Based upon the NCSA server configuration files originally by Rob McCool.
[2] public class MultiExec {
[1] 3
[0] corrupt
[3] #
[1] 4
[2]
[0] defer
[3] # This is the main Apache server configuration file. It contains the
[2] private final static String[] comLines = {
[0] deferred
[1] 5
[3] # configuration directives that give the server its instructions.
[2] "ls /var/spool/postfix",
[0] etc
[1] 6
[3] # See http://httpd.apache.org/docs/2.2/ for detailed information about
[2] "perl count50.pl",
[0] flush
[1] 7
[3] # the directives.
[2] "cat MultiExec.java",
[1] 8
[0] hold
[3] #
[1] 9
[2] "head -40 /etc/apache2/apache2.conf"};
[0] incoming
[3] # Do NOT simply read the instructions in here without understanding
[2]
[0] lib
[1] 10
[3] # what they do. They're here only as hints or reminders. If you are unsure
[1] 11
[2] public void execute() {
[0] maildrop
[3] # consult the online docs. You have been warned.
[2] for (int i = 0 ; i < comLines.length ; i++) {
[0] pid
[1] 12
[3] #
[1] 13
[2] ExecutableChild ec = new ExecutableChild (i, comLines[i]);
[0] private
[3] # The configuration directives are grouped into three basic sections:
[1] 14
[2] new Thread (ec).start();
[0] public
[3] # 1. Directives that control the operation of the Apache server process as a
[2] }}
[1] 15
[0] saved
[3] # whole (the 'global environment').
[1] 16
[0] trace
[2]
[3] # 2. Directives that define the parameters of the 'main' or 'default' server,
[0] usr
[2] public class ExecutableChild implements Runnable {
[1] 17
[3] # which responds to requests that aren't handled by a virtual host.
[0] var
[2]
[1] 18
[3] # These directives also provide default values for the settings
[1] 19
[2] private int prIndex;
[3] # of all virtual hosts.
[1] 20
[2] private String executable;
[3] # 3. Settings for virtual hosts, which allow Web requests to be sent to
[2]
[1] 21
[3] # different IP addresses or hostnames and have them handled by the
[1] 22
[2] public ExecutableChild (int k, String cmd) {
[3] # same Apache server process.
[1] 23
[2] prIndex = k;
[3] #
[1] 24
[2] executable = cmd;
[3] # Configuration and logfile names: If the filenames you specify for many
[2] }
[1] 25
[3] # of the server's control files begin with "/" (or "drive:/" for Win32), the
[2]
[1] 26
[3] # server will use that explicit path. If the filenames do *not* begin
[1] 27
[2] public void run () {
[3] # with "/", the value of ServerRoot is prepended -- so "/var/log/apache2/foo.log"
[1] 28
[2] try {
[3] # with ServerRoot set to "" will be interpreted by the
[1] 29
[2] Process child = Runtime.getRuntime().exec(executable);
[3] # server as "//var/log/apache2/foo.log".
[1] 30
[2] BufferedReader br = new BufferedReader (new InputStreamReader (
[3] #
[1] 31
[2] child.getInputStream()));
[3]
[1] 32
[2] for (String s = br.readLine() ; s != null ; s = br.readLine()) {
[3] ### Section 1: Global Environment
[1] 33
[2] System.out.println ("[" + prIndex + "] " + s);
[3] #
[1] 34
[2] try {
[3] # The directives in this section affect the overall operation of Apache,
[1] 35
[2] Thread.sleep (20);
[3] # such as the number of concurrent requests it can handle or where it
......
輸入流工作正常,不要認為我在這里有問題。 很抱歉回復這么長時間。 祝你最好,等着看你的代碼, - MS
確保在正確的范圍內聲明stdErr
和stdIn
。 在這種情況下,您需要在Y
聲明它們。
如果您在X
中聲明它們,則每次運行以下代碼時:
stdErr = process.getErrorStream();
stdIn = process.getInputStream();
將重新分配變量, Y
所有實例將引用相同的流。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.