Tôi đã triển khai điều này bằng cách sử dụng ba cách tiếp cận được đề xuất đi kèm với ví dụ mã chi tiết (Tôi là người mới làm quen với lập trình luồng và những mã ví dụ này là vô giá - tôi vẫn sẽ vò đầu bứt tai không biết làm thế nào nếu nó chỉ được giải thích bằng tiếng Anh không có mã).
Tôi đã triển khai lớp tiện ích mà tôi đang sử dụng cho việc này với ba phương thức để thực thi một lệnh có thời gian chờ như sau:
package com.abc.network.lifecycle.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class ProcessUtility
{
static Log log = LogFactory.getLog(ProcessUtility.class);
private static class Worker
extends Thread
{
private final Process process;
private Integer exitValue;
Worker(final Process process)
{
this.process = process;
}
public Integer getExitValue()
{
return exitValue;
}
@Override
public void run()
{
try
{
exitValue = process.waitFor();
}
catch (InterruptedException ignore)
{
return;
}
}
}
public static int executeCommandWithExecutors(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec(massagedCommand);
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
Callable<Integer> call = new Callable<Integer>()
{
public Integer call()
throws Exception
{
process.waitFor();
return process.exitValue();
}
};
Future<Integer> futureResultOfCall = Executors.newSingleThreadExecutor().submit(call);
try
{
int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS);
return exitValue;
}
catch (TimeoutException ex)
{
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (ExecutionException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
public static int executeCommandWithSleep(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(massagedCommand);
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
final boolean[] flags = { true };
new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(timeOut);
}
catch (InterruptedException ex)
{
String errorMessage = "Timeout loop thread unexpectedly interrupted.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
flags[0] = false;
}
}.start();
int returnValue = -1;
while (flags[0] && (returnValue < 0))
{
returnValue = process.waitFor();
}
if (returnValue < 0)
{
log.warn("The command [" + command + "] did not complete before the timeout period expired (timeout: " +
timeOut + " ms)");
}
return returnValue;
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
public static int executeCommandWithWorker(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(massagedCommand);
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
Worker worker = new Worker(process);
worker.start();
try
{
worker.join(timeOut);
Integer exitValue = worker.getExitValue();
if (exitValue != null)
{
return exitValue;
}
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
catch (InterruptedException ex)
{
worker.interrupt();
Thread.currentThread().interrupt();
throw ex;
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
private static String validateSystemAndMassageCommand(final String originalCommand)
{
if (originalCommand.isEmpty() || (originalCommand.length() < 1))
{
String errorMessage = "Missing or empty command line parameter.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
String massagedCommand;
String osName = System.getProperty("os.name");
if (osName.equals("Windows XP"))
{
massagedCommand = "cmd.exe /C " + originalCommand;
}
else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux"))
{
massagedCommand = originalCommand;
}
else
{
String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" +
osName + "\').";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
return massagedCommand;
}
}
Tôi đã tạo một lớp để sử dụng và hiển thị các luồng đầu ra và lỗi từ một lệnh (lấy từ http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4 ):
package com.abc.network.lifecycle.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
class StreamGobbler
extends Thread
{
static private Log log = LogFactory.getLog(StreamGobbler.class);
private InputStream inputStream;
private String streamType;
private boolean displayStreamOutput;
StreamGobbler(final InputStream inputStream,
final String streamType,
final boolean displayStreamOutput)
{
this.inputStream = inputStream;
this.streamType = streamType;
this.displayStreamOutput = displayStreamOutput;
}
@Override
public void run()
{
try
{
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null)
{
if (displayStreamOutput)
{
System.out.println(streamType + ">" + line);
}
}
}
catch (IOException ex)
{
log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
ex.printStackTrace();
}
}
}
Tôi đã tạo một lệnh kiểm tra mất khoảng 10 giây để hoàn thành:
#!/bin/bash
sleep 10
echo 'TEST COMMAND RAN OK'
Sau đó, tôi đã tạo một chương trình thử nghiệm để kiểm tra ba phương pháp khác nhau, gọi mỗi phương thức với giá trị thời gian chờ là 5 giây (lệnh sẽ thất bại) và với giá trị thời gian chờ là 15 giây (lệnh sẽ thành công):
package com.abc.network.lifecycle.util;
public class ProcessUtilityTester
{
public static void main(final String[] args)
{
try
{
String command = args[0];
int exitValue = -1;
System.out.println("\n\n5000ms timeout With Executors:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 5000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
System.out.println("\n\n5000ms timeout With Sleep:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 5000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
System.out.println("\n\n5000ms timeout With Worker:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 5000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
System.out.println("\n\n15000ms timeout With Executors:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithExecutors(command, true, true, 15000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
System.out.println("\n\n15000ms timeout With Sleep:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithSleep(command, true, true, 15000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
System.out.println("\n\n15000ms timeout With Worker:");
try
{
exitValue = -1;
exitValue = ProcessUtility.executeCommandWithWorker(command, true, true, 15000);
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("\nExit value:" + exitValue);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
System.exit(0);
}
}
}
Đây là những gì tôi thấy khi chạy chương trình thử nghiệm:
5000ms timeout With Executors:
May 1, 2009 1:55:19 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithExecutors
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
at java.util.concurrent.FutureTask.get(FutureTask.java:91)
at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:186)
at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:19)
Caused by: java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
at java.util.concurrent.FutureTask.get(FutureTask.java:91)
at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithExecutors(ProcessUtility.java:179)
... 1 more
Exit value:-1
5000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK
Exit value:0
5000ms timeout With Worker:
May 1, 2009 1:55:34 AM com.abc.network.lifecycle.util.ProcessUtility executeCommandWithWorker
SEVERE: The command [/tmp/testcmd.sh] timed out.
java.lang.RuntimeException: The command [/tmp/testcmd.sh] timed out.
at com.abc.network.lifecycle.util.ProcessUtility.executeCommandWithWorker(ProcessUtility.java:338)
at com.abc.network.lifecycle.util.ProcessUtilityTester.main(ProcessUtilityTester.java:47)
Exit value:-1
15000ms timeout With Executors:
OUTPUT>TEST COMMAND RAN OK
OUTPUT>TEST COMMAND RAN OK
Exit value:0
15000ms timeout With Sleep:
OUTPUT>TEST COMMAND RAN OK
Exit value:0
15000ms timeout With Worker:
OUTPUT>TEST COMMAND RAN OK
Exit value:0
Vì vậy, từ những gì tôi có thể cho biết phương pháp sử dụng lớp luồng Worker hoạt động tốt nhất, trong đó nó mang lại kết quả mong đợi trong cả hai trường hợp. Cách tiếp cận sử dụng Executor cũng hoạt động như mong đợi, với cảnh báo rằng nó dường như đang chạy lệnh hai lần trong trường hợp timout 15000ms (tức là tôi thấy đầu ra cho lệnh hai lần). Cách tiếp cận sử dụng phương thức sleep () không hết thời gian lệnh như mong đợi trong trường hợp thời gian chờ 5000ms và hiển thị đầu ra hai lần, nhưng chạy lệnh như mong đợi trong trường hợp thời gian chờ 15000ms.