Generally, when we run methods in other classes in Java, whether they are static or dynamic calls, they are executed in the current process, that is, there is only one java virtual machine instance running. Sometimes, we need to start multiple java subprocesses through java code. Although doing this takes up some system resources, it will make the program more stable because the newly started program runs in different virtual machine processes. If an exception occurs in one process, it will not affect other child processes.
In Java we can use two methods to achieve this requirement. The easiest way is to execute java classname through the exec method in Runtime. If the execution is successful, this method returns a Process object. If the execution fails, an IOException error will be thrown. Let's take a look at a simple example below.
// Test1.java file import java.io.*;public class Test{public static void main(String[] args){FileOutputStream fOut = new FileOutputStream("c://Test1 .txt");fOut.close() ;System.out.println("Called successfully!");}}// Test_Exec.javapublic class Test_Exec{public static void main(String[] args){Runtime run = Runtime.ge tRuntime();Process p = run. exec("java test1");}}After running the program through java Test_Exec, I found that there was an additional Test1.txt file on the C drive, but the output information "Called successfully!" did not appear in the console. Therefore, it can be concluded that Test has been executed successfully, but for some reason, the output information of Test is not output in the console of Test_Exec. This reason is also very simple, because the child process of Test_Exec is created using exec. This child process does not have its own console, so it does not output any information.
If you want to output the output information of the child process, you can obtain the output stream of the child process through getInputStream in Process (output in the child process, input in the parent process), and then transfer the output stream of the child process from the parent process's console Output. The specific implementation code is as follows:
// Test_Exec_Out.javaimport java.io.*;public class Test_Exec_Out{public static void main(String[] args){Runtime run = Runtime.getRuntime ();Process p = run.exec("java test1");BufferedInputStream in = new BufferedInputStream(p.getInputStream());BufferedReader br = new BufferedReader(new InputStreamReader(in));String s;while ((s = br.readLine()) != null)System.out.println(s) ;}}
As can be seen from the above code, in Test_Exec_Out.java, the output information of the child process is read by row, and then the output is performed on each line in Test_Exec_Out. The above discussion is how to get the output information of the child process. Then, in addition to output information, there is also input information. Since the child process does not have its own console, the input information must also be provided by the parent process. We can provide input information to the child process through the getOutputStream method of Process (that is, input information from the parent process to the child process, rather than input information from the console). We can look at the following code:
// Test2.java file import java.io.*;public class Test{public static void main(String[] args){BufferedReader br = new BufferedReader(new InputStreamRead er(System.in));System.out.println(" Information entered by the parent process: " + br.readLine());}}// Test_Exec_In.javaimport java.io.*;public class Test_Exec_In{public static void main(String[] args){Run time run = Runtime.getRuntime ();Process p = run.exec("java test2");BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));bw.write("Output to child process information");bw.flush( );bw.close(); // The stream must be closed, otherwise information cannot be entered into the child process// System.in.read(); }} From the above code, we can see that Test1 gets the information sent by Test_Exec_In and outputs it. When you do not add bw.flash() and bw.close(), the information will not reach the child process, which means that the child process enters a blocking state, but since the parent process has exited, the child process also exits. If you want to prove this, you can add System.in.read() at the end and then view the java process through the task manager (under windows), and you will find that if you add bw.flush() and bw.close() , only one java process exists, if they are removed, two java processes exist. This is because, if the information is passed to Test2, Test2 exits after obtaining the information. Here is one thing to be explained that the execution of exec is asynchronous and will not stop executing the following code because a certain program that is executed is blocked. Therefore, after running test2, the following code can still be executed.
The exec method has been reloaded many times. What is used above is just an overload of it. It can also separate commands and parameters, such as exec("java.test2") can be written as exec("java", "test2"). Exec can also run Java virtual machines with different configurations through specified environment variables.
In addition to using Runtime's exec method to build a child process, you can also build a child process through ProcessBuilder. The use of ProcessBuilder is as follows:
// Test_Exec_Out.javaimport java.io.*;public class Test_Exec_Out{public static void main(String[] args){ProcessBuilder pb = new ProcessBui lder("java", "test1");Process p = pb.start(); … … }}In establishing child processes, ProcessBuilder is similar to Runtime. Different ProcessBuilders use the start() method to start the child process, while Runtime uses the exec method to start the child process. After getting the Process, their operations are exactly the same.
Like Runtime, ProcessBuilder can also set the environment information, working directory, etc. of the executable file. The following example describes how to set this information using ProcessBuilder.
ProcessBuilder pb = new ProcessBuilder("Command", "arg2", "arg2", ''');// Set environment variable Map<String, String> env = pb.environment();env.put("key1", : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : "value1");env.remove("key2");env.put("key2", env.get("key1") + "_test");pb.directory("../abcd"); // Set the working directory Process p = pb.start(); // Create a child process Process blocking problem
Processes represented by Process sometimes do not work well on some platforms, especially when operating on standard input streams, output streams and error outputs representing processes. If used carelessly, the process may block. Even deadlock.
If the statement in the above example that rereads information from standard output is modified to read from the error output stream:
stdout = new BufferedReader(new InputStreamReader(p.getErrorStream()));
Then the program will block and cannot be executed, but hang there.
When the process starts, the standard output stream and the error output stream are turned on to prepare the output, and when the process ends, they are closed. In the above example, the error output stream has no data to be output, and the standard output stream has data output. Since the data in the standard output stream is not read, the process will not end and the error output stream will not be closed. Therefore, when the readLine() method is called, the entire program will be blocked. To solve this problem, you can first read the standard output stream and then read the wrong output stream according to the actual order of the output.
However, in many cases, the output sequence cannot be clearly known, especially when standard input is required, the situation will be more complicated. At this time, threads can be used to process standard output, error output and standard input separately, and the stream or data can be read according to their business logic relationship.
For problems caused by standard output streams and erroneous output streams, you can use ProcessBuilder's redirectErrorStream() method to combine them into one. At this time, just read the standard output data.
When using Process's waitFor() method in a program, especially when calling the waitFor() method before reading, it may also cause blockage. You can use thread methods to solve this problem, or you can call waitFor() method after reading data to wait for the program to end.
In short, here I introduce the use of the ProcessBuilder class, using the redirectErrorStream method to combine the standard output stream and the error output stream into one. After starting the process with the start() method, read the data from the standard output first, and then Call waitFor() method to wait for the process to end.
like:
import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public cla ss Test3 { public static void main(String[] args) { try { List<String> list = new ArrayList<String>(); ProcessBuilder pb = null; Process p = null; String line = null; BufferedReader stdout = null; // list the files and directories under C:/ list.add ("CMD.EXE"); list.add("/C"); list.add("dir1"); pb = new ProcessBuilder(list); pb.directory(new File("C://")) ; //merge the error output with the standard output pb.redirectErrorStream(true); p = pb.start(); //read the standard output stdout = new BufferedReader(new InputStreamReader(p .getInputStream())); while ( (line = stdout.readLine()) != null) { System.out.println(line); } int ret = p.waitFor(); System.out.println("the return code is " + ret); stdou t .close(); } catch (Exception e) { e.printStackTrace(); } }