当前位置: 首页 > 编程笔记 >

浅谈Java多进程程序的运行模式

鲜于光辉
2023-03-14
本文向大家介绍浅谈Java多进程程序的运行模式,包括了浅谈Java多进程程序的运行模式的使用技巧和注意事项,需要的朋友参考一下

一般我们在java中运行其它类中的方法时,无论是静态调用,还是动态调用,都是在当前的进程中执行的,也就是说,只有一个java虚拟机实例在运行。而有的时候,我们需要通过java代码启动多个java子进程。这样做虽然占用了一些系统资源,但会使程序更加稳定,因为新启动的程序是在不同的虚拟机进程中运行的,如果有一个进程发生异常,并不影响其它的子进程。

在Java中我们可以使用两种方法来实现这种要求。最简单的方法就是通过Runtime中的exec方法执行java classname。如果执行成功,这个方法返回一个Process对象,如果执行失败,将抛出一个IOException错误。下面让我们来看一个简单的例子。

// Test1.java文件
import java.io.*;
public class Test
{
public static void main(String[] args)
{
FileOutputStream fOut = new FileOutputStream("c://Test1.txt");
fOut.close();
System.out.println("被调用成功!");
}
}

// Test_Exec.java
public class Test_Exec
{
public static void main(String[] args)
{
Runtime run = Runtime.getRuntime();
Process p = run.exec("java test1");
}
}

通过java Test_Exec运行程序后,发现在C盘多了个Test1.txt文件,但在控制台中并未出现"被调用成功!"的输出信息。因此可以断定,Test已经被执行成功,但因为某种原因,Test的输出信息未在Test_Exec的控制台中输出。这个原因也很简单,因为使用exec建立的是Test_Exec 的子进程,这个子进程并没有自己的控制台,因此,它并不会输出任何信息。

如果要输出子进程的输出信息,可以通过Process中的getInputStream得到子进程的输出流(在子进程中输出,在父进程中就是输入),然后将子进程中的输出流从父进程的控制台输出。具体的实现代码如下如示:

// 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);
}
}


从上面的代码可以看出,在Test_Exec_Out.java中通过按行读取子进程的输出信息,然后在Test_Exec_Out中按每行进行输出。上面讨论的是如何得到子进程的输出信息。那么,除了输出信息,还有输入信息。既然子进程没有自己的控制台,那么输入信息也得由父进程提供。我们可以通过 Process的getOutputStream方法来为子进程提供输入信息(即由父进程向子进程输入信息,而不是由控制台输入信息)。我们可以看看如下的代码:

// Test2.java文件
import java.io.*;
public class Test
{
public static void main(String[] args)
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("由父进程输入的信息:" + 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("向子进程输出信息");
bw.flush();
bw.close(); // 必须得关闭流,否则无法向子进程中输入信息
// System.in.read();
 }
}

从以上代码可以看出,Test1得到由Test_Exec_In发过来的信息,并将其输出。当你不加bw.flash()和bw.close()时,信息将无法到达子进程,也就是说子进程进入阻塞状态,但由于父进程已经退出了,因此,子进程也跟着退出了。如果要证明这一点,可以在最后加上 System.in.read(),然后通过任务管理器(在windows下)查看java进程,你会发现如果加上bw.flush()和 bw.close(),只有一个java进程存在,如果去掉它们,就有两个java进程存在。这是因为,如果将信息传给Test2,在得到信息后, Test2就退出了。在这里有一点需要说明一下,exec的执行是异步的,并不会因为执行的某个程序阻塞而停止执行下面的代码。因此,可以在运行 test2后,仍可以执行下面的代码。
exec方法经过了多次的重载。上面使用的只是它的一种重载。它还可以将命令和参数分开,如exec("java.test2")可以写成exec("java", "test2")。exec还可以通过指定的环境变量运行不同配置的java虚拟机。

除了使用Runtime的exec方法建立子进程外,还可以通过ProcessBuilder建立子进程。ProcessBuilder的使用方法如下:

// 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();
… …
}
}

在建立子进程上,ProcessBuilder和Runtime类似,不同的ProcessBuilder使用start()方法启动子进程,而Runtime使用exec方法启动子进程。得到Process后,它们的操作就完全一样的。

ProcessBuilder和Runtime一样,也可设置可执行文件的环境信息、工作目录等。下面的例子描述了如何使用ProcessBuilder设置这些信息。

ProcessBuilder pb = new ProcessBuilder("Command", "arg2", "arg2", ''');
// 设置环境变量
Map<String, String> env = pb.environment();
env.put("key1", "value1");
env.remove("key2");
env.put("key2", env.get("key1") + "_test");
pb.directory("../abcd"); // 设置工作目录
Process p = pb.start(); // 建立子进程

进程阻塞问题 
  由Process代表的进程在某些平台上有时候并不能很好的工作,特别是在对代表进程的标准输入流、输出流和错误输出进行操作时,如果使用不慎,有可能导致进程阻塞,甚至死锁。
如果将以上事例中的从标准输出重读取信息的语句修改为从错误输出流中读取: 

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

  那么程序将发生阻塞,不能执行完成,而是hang在那里。
  当进程启动后,就会打开标准输出流和错误输出流准备输出,当进程结束时,就会关闭他们。在以上例子中,错误输出流没有数据要输出,标准输出流中有数据输出。由于标准输出流中的数据没有被读取,进程就不会结束,错误输出流也就不会被关闭,因此在调用readLine()方法时,整个程序就会被阻塞。为了解决这个问题,可以根据输出的实际先后,先读取标准输出流,然后读取错误输出流。
   但是,很多时候不能很明确的知道输出的先后,特别是要操作标准输入的时候,情况就会更为复杂。这时候可以采用线程来对标准输出、错误输出和标准输入进行分别处理,根据他们之间在业务逻辑上的关系决定读取那个流或者写入数据。
   针对标准输出流和错误输出流所造成的问题,可以使用ProcessBuilder的redirectErrorStream()方法将他们合二为一,这时候只要读取标准输出的数据就可以了。
当在程序中使用Process的waitFor()方法时,特别是在读取之前调用waitFor()方法时,也有可能造成阻塞。可以用线程的方法来解决这个问题,也可以在读取数据后,调用waitFor()方法等待程序结束。
总之,解决阻塞的方法在这里我介绍使用ProcessBuilder类,利用redirectErrorStream方法将标准输出流和错误输出流合二为一,在用start()方法启动进程后,先从标准输出中读取数据,然后调用waitFor()方法等待进程结束。
如:

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(); 
  } 
} 

 类似资料:
  • 本文向大家介绍浅谈小程序 setData学问多,包括了浅谈小程序 setData学问多的使用技巧和注意事项,需要的朋友参考一下 为什么不能频繁 setData 先科普下 setData 做的事情: 在数据传输时,逻辑层会执行一次 JSON.stringify 来去除掉 setData 数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将 setData 所设置的数据字段与 data 合

  • 本文向大家介绍浅谈Java编程中的单例设计模式,包括了浅谈Java编程中的单例设计模式的使用技巧和注意事项,需要的朋友参考一下 写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据。但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像findbugs等代码检查工具还会认为使用System.out.print

  • 本文向大家介绍浅谈Java的两种多线程实现方式,包括了浅谈Java的两种多线程实现方式的使用技巧和注意事项,需要的朋友参考一下 本文介绍了浅谈Java的两种多线程实现方式,分享给大家。具有如下: 一、创建多线程的两种方式 Java中,有两种方式可以创建多线程: 1 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2 通过实现Runnable接口,实例化Threa

  • 本文向大家介绍浅谈chuck-lua中的多线程,包括了浅谈chuck-lua中的多线程的使用技巧和注意事项,需要的朋友参考一下 chuck-lua支持actor模式的线程模型.可以通过cthread.new创建线程,然后通过cthread.sendmail向线程发送消息. 与skynet这种框架不同,chuck-lua并不提供多线程的任务/消息调度功能,每个线程维护了一个简单的线程邮箱,用于缓存其

  • 本文向大家介绍浅谈C++模板元编程,包括了浅谈C++模板元编程的使用技巧和注意事项,需要的朋友参考一下 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文)。模板元编程的根在模板。模板的使命很简单:为自动代码生成提供方便。提高程序员生产率的一个非常有效

  • 本文向大家介绍浅谈Java文件执行顺序、main程序入口的理解,包括了浅谈Java文件执行顺序、main程序入口的理解的使用技巧和注意事项,需要的朋友参考一下 在我们通过JVM编译Java后缀名的文件时,JVM首先寻找入口(main方法) 1、由于在入口时,未调用任何对象,该方法只能设置为static静态 2、JVM为Java的最底层,所以即使有返回结果,结果也无处可去,因此该方法必然是void无

  • 万事知其然,要知其所以然,所以本节带大家来详细了解一下 Java 程序的执行过程。 从《 使用记事本编写运行Java程序》一节的案例可以看出, Java 程序的运行必须经过编写、编译和运行 3 个步骤。 编写:是指在 Java 开发环境中进行程序代码的输入,最终形成后缀名为 .java 的 Java 源文件。 编译:是指使用 Java 编译器对源文件进行错误排査的过程,编译后将生成后缀名为 .cl

  • 本文向大家介绍简单谈谈python中的多进程,包括了简单谈谈python中的多进程的使用技巧和注意事项,需要的朋友参考一下 进程是由系统自己管理的。 1:最基本的写法 2、实际上是通过os.fork的方法产生进程的 unix中,所有进程都是通过fork的方法产生的。 3、线程共享内存 进程不共享内存: 若想共享内存,需使用multiprocessing模块中的Queue 4、锁:仅是对于屏幕的共享