Talk about the running mode of Java multiprocess program

  • 2020-04-01 04:22:01
  • OfStack

Typically when we run methods in other classes in Java, both static and dynamic calls are executed in the current process; that is, there is only one instance of the Java virtual machine running. Sometimes, however, we need to start multiple Java child processes through Java code. Although this takes up some system resources, it makes the program more stable, because the newly started program runs in a different virtual machine process, and if one process fails, it does not affect the other child processes.

There are two ways to do this in Java. The easiest way is to execute the Java classname through the exec method in Runtime. This method returns a Process object on success and throws an IOException error on failure. Let's look at a simple example.


//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.java
public class Test_Exec
{
public static void main(String[] args)
{
Runtime run = Runtime.getRuntime();
Process p = run.exec("java test1");
}
}

After running the program through Java Test_Exec, I found that there was an extra test1.txt file in disk C, but there was no "called successfully!" in the console. Output information. Therefore, it can be concluded that the Test has been executed successfully, but for some reason, the output of the Test is not printed in the Test_Exec console. The reason is also simple, because exec is used to create a child of Test_Exec, which does not have its own console, so it does not output any information.

If you want to output the output of the child Process, you can get the output stream of the child Process (output in the child Process, input in the parent Process) from the getInputStream in the Process, and then output the output stream in the child Process from the console of the parent Process. The specific implementation code is as follows:


// Test_Exec_Out.java
import 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 you can see from the code above, the output of the child process is read by line in test_exec_out.java, and then output by line in Test_Exec_Out. The above is how to get the output information of the child process. So, in addition to the output information, there's also the input information. Since the child process does not have its own console, the input information is also provided by the parent process. We can provide input information to the child Process through the getOutputStream method of Process (that is, the parent Process enters information to the child Process rather than 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 InputStreamReader(System.in));
System.out.println(" Information entered by the parent process: " + br.readLine());
}
}

// Test_Exec_In.java
import java.io.*;
public class Test_Exec_In
{
public static void main(String[] args)
{
Runtime run = Runtime.getRuntime();
Process p = run.exec("java test2");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
bw.write(" Output information to the child process ");
bw.flush();
bw.close(); //You must close the stream or you cannot enter information into the child process
// System.in.read();
 }
}

As you can see from the above code, Test1 takes 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, that is, the child process is in a blocking state, but since the parent process has exited, so the child process exits. To prove this, add system.in.read () at the end and then look at the Java process through the task manager (under Windows). You'll see that if you add bw.flush() and bw.close(), only one Java process exists, and if you remove them, two Java processes exist. This is because if you pass the information to Test2, after you get the information, Test2 exits. It is important to note here that exec execution is asynchronous and does not stop execution of the following code because of a program block. Therefore, you can run test2 and still execute the following code.
The exec method has been overloaded multiple times. This is just one of its overloads. It also separates commands from arguments, such as exec("java.test2"), which can be written as exec(" Java ", "test2"). Exec can also run different configured Java virtual machines with the specified environment variables.

In addition to using the exec method of Runtime to set up the child process, you can set up the child process through the ProcessBuilder. The ProcessBuilder is used as follows:


// Test_Exec_Out.java
import java.io.*;
public class Test_Exec_Out
{
public static void main(String[] args)
{
ProcessBuilder pb = new ProcessBuilder("java", "test1");
Process p = pb.start();
 ...   ... 
}
}

The ProcessBuilder is similar to the Runtime in establishing the child process, with different processbuilders using the start() method to start the child process, and the Runtime using the exec method to start the child process. Once you have Process, they operate exactly the same.

The ProcessBuilder, like the Runtime, also sets the environment information for the executable, the working directory, and so on. The following example describes how to set this information using the ProcessBuilder.


ProcessBuilder pb = new ProcessBuilder("Command", "arg2", "arg2", ''');
//Setting environment variables
Map<String, String> env = pb.environment();
env.put("key1", "value1");
env.remove("key2");
env.put("key2", env.get("key1") + "_test");
pb.directory("../abcd"); //Set up the working directory
Process p = pb.start(); //Create child process

Process blocking problem  
  Processes represented by Process sometimes don't work well on some platforms, especially when operating on the standard input stream, output stream, and error output that represent the Process, which can cause the Process to block or even deadlock if used improperly.
If the statement in the above example that rereads information from standard output is changed to read from the error output stream:  


  stdout = new BufferedReader(new InputStreamReader(p.getErrorStream())); 

  Then the program will block, not finish, but hang there.
  When the process starts, the standard output stream and the error output stream are opened to prepare the output, and when the process ends, they are closed. In the above example, the error output stream has no data to output, and the standard output stream has data output. Because the data in the standard output stream is not read, the process does not end and the error output stream is not closed, so the entire program is blocked when the readLine() method is called. To solve this problem, you can read the standard output stream first and then the error output stream, depending on the actual order of the output.
    However, many times it is difficult to know the sequence of outputs clearly, especially when operating on standard inputs. In this case, threads can be used to handle standard output, error output, and standard input separately, depending on their relationship in the business logic to determine which stream to read or write data.
    For problems caused by the standard output stream and the error output stream, you can combine them using the redirectErrorStream() method of the ProcessBuilder and simply read the data from the standard output.
It is also possible to block when using the waitFor() method of Process in a program, especially when calling the waitFor() method before reading. You can solve this problem with threaded methods or, after reading the data, call the waitFor() method and waitFor the program to finish.
Anyway, the way to solve the block is to use the ProcessBuilder class, which USES the redirectErrorStream method to merge the standard and error output streams into one. After starting the process with the start() method, read the data from the standard output and then call the waitFor() method to waitFor the process to finish.
Such as:


import java.io.BufferedReader; 
import java.io.File; 
import java.io.InputStreamReader; 
import java.util.ArrayList; 
import java.util.List; 
 
public class 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 directorys 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); 
  stdout.close(); 
  } catch (Exception e) { 
  e.printStackTrace(); 
  } 
} 


Related articles: