java多进程实现

java多进程实现

原文链接: https://blog.youkuaiyun.com/weixin_28902749/article/details/114032221

​ 看了下网上大多对多线程实现多一些,前阵子遇到了多进程,就记录一下,顺便自己也加深一下理解。

1、java创建进程以及启动

java为进程的创建以及启动提供了两种方式。

  • 使用Runtime的exec()方法启动进程
  • 使用ProcessBuilder的start()方法启动进程

1.1、ProcessBuilder

	ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在J2SE 1.5之前,都是由Process类处来实现进程的控制管理。

​ 每个 ProcessBuilder 实例管理一个进程属性集。start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。

每个进程生成器管理这些进程属性:

  • 命令

    • 是一个字符串列表,它表示要调用的外部程序文件及其参数(如果有)。在此,表示有效的操作系统命令的字符串列表是依赖于系统的。
  • 环境

    • 是从变量 到值 的依赖于系统的映射。初始值是当前进程环境的一个副本(请参阅 System.getenv())。
  • 工作目录

    • 默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名。
  • redirectErrorStream 属性

    • 最初,此属性为 false,意思是子进程的标准输出和错误输出被发送给两个独立的流,这些流可以通过 Process.getInputStream() 和 Process.getErrorStream() 方法来访问。如果将值设置为 true,标准错误将与标准输出合并。这使得关联错误消息和相应的输出变得更容易。在此情况下,合并的数据可从 Process.getInputStream() 返回的流读取,而从 Process.getErrorStream() 返回的流读取将直接到达文件尾。

      注意: 修改进程构建器的属性将影响后续由该对象的 start() 方法启动的进程,但从不会影响以前启动的进程或 Java 自身的进程。大多数错误检查由 start() 方法执行。可以修改对象的状态,但这样 start() 将会失败。例如,将命令属性设置为一个空列表将不会抛出异常,除非包含了 start()。

      注意: 此类不是同步的。如果多个线程同时访问一个 ProcessBuilder,而其中至少一个线程从结构上修改了其中一个属性,它必须 保持外部同步。

构造子进程方法摘要:

  • ProcessBuilder(List command)

    • 利用指定的操作系统程序和参数构造一个进程生成器。
  • ProcessBuilder(String command)

    • 利用指定的操作系统程序和参数构造一个进程生成器。

ProcessBuilder类方法摘要:

  • List command()

    • 返回此进程生成器的操作系统程序和参数。
  • ProcessBuilder command(List command)

    • 设置此进程生成器的操作系统程序和参数。
  • ProcessBuilder command(String… command)

    • 设置此进程生成器的操作系统程序和参数。
  • File directory()

    • 返回此进程生成器的工作目录。
  • ProcessBuilder directory(File directory)

    • 设置此进程生成器的工作目录。
  • Map environment()

    • 返回此进程生成器环境的字符串映射视图。
  • boolean redirectErrorStream()

    • 通知进程生成器是否合并标准错误和标准输出。
  • ProcessBuilder redirectErrorStream(boolean redirectErrorStream)

    • 设置此进程生成器的 redirectErrorStream 属性。
  • Process start()

    • 使用此进程生成器的属性启动一个新进程。

示例代码:

1.2、 Runtime

	每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime() 方法获取当前运行时,如下操作。
Runtime run = Runtime.getRuntime();

**注意:**应用程序不能创建自己的 Runtime 类实例。但可以通过 getRuntime () 方法获取当前Runtime运行时对象的引用。一旦得到了一个当前的Runtime对象的引用,就可以调用Runtime对象的方法去控制Java虚拟机的状态和行为

Runtime对象引用方法摘要:

  • void addShutdownHook(Thread hook)
    • 注册新的虚拟机来关闭挂钩。
  • int availableProcessors()
    • 向 Java 虚拟机返回可用处理器的数目。
  • Process exec(String command)
    • 在单独的进程中执行指定的字符串命令。
  • Process exec(String[] cmdarray)
    • 在单独的进程中执行指定命令和变量。
  • Process exec(String[] cmdarray, String[] envp)
    • 在指定环境的独立进程中执行指定命令和变量。
  • Process exec(String[] cmdarray, String[] envp, File dir)
    • 在指定环境和工作目录的独立进程中执行指定的命令和变量。
  • Process exec(String command, String[] envp)
    • 在指定环境的单独进程中执行指定的字符串命令。
  • Process exec(String command, String[] envp, File dir)
    • 在有指定环境和工作目录的独立进程中执行指定的字符串命令。
  • void exit(int status)
    • 通过启动虚拟机的关闭序列,终止当前正在运行的 Java 虚拟机。
  • long freeMemory()
    • 返回 Java 虚拟机中的空闲内存量。
  • void gc()
    • 运行垃圾回收器。
  • static Runtime getRuntime()
    • 返回与当前 Java 应用程序相关的运行时对象。
  • void halt(int status)
    • 强行终止目前正在运行的 Java 虚拟机。
  • void load(String filename)
    • 加载作为动态库的指定文件名。
  • void loadLibrary(String libname)
    • 加载具有指定库名的动态库。
  • long maxMemory()
    • 返回 Java 虚拟机试图使用的最大内存量。
  • boolean removeShutdownHook(Thread hook)
    • 取消注册某个先前已注册的虚拟机关闭挂钩。
  • void runFinalization()
    • 运行挂起 finalization 的所有对象的终止方法。
  • static void runFinalizersOnExit(boolean value)
    • 已过时。 此方法本身具有不安全性。它可能对正在使用的对象调用终结方法,而其他线程正在操作这些对象,从而导致不正确的行为或死锁。
  • long totalMemory()
    • 返回 Java 虚拟机中的内存总量。
  • void traceInstructions(boolean on)
    • 启用/禁用指令跟踪。
  • void traceMethodCalls(boolean on)
    • 启用/禁用方法调用跟踪。

1.3、 Process

不管通过那种方法启动进程后,都会返回一个Process类的实例代表启动的进程,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法:

  • void destroy()
    • 杀掉子进程。一般情况下,该方法并不能杀掉已经启动的进程,不用为好。
  • int exitValue()
    • 返回子进程的出口值。只有启动的进程执行完成、或者由于异常退出后,exitValue()方法才会有正常的返回值,否则抛出异常。
  • InputStream getErrorStream()
    • 获取子进程的错误流。如果错误输出被重定向,则不能从该流中读取错误输出。
  • InputStream getInputStream()
    • 获取子进程的输入流。可以从该流中读取进程的标准输出。
  • OutputStream getOutputStream()
    • 获取子进程的输出流。写入到该流中的数据作为进程的标准输入。
  • int waitFor()
    • 导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。

2、多进程编程实例

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

2.1、ProcessBuilder 示例

如果执行成功,这个方法返回一个Process对象,如果执行失败,将抛出一个IOException错误。

TestProcess.java文件

import java.io.*;

public class TestProcess {
	public static void main(String[] args) {
        //创建一个进程,调用Test1.java文件
        ProcessBuilder pb = new ProcessBuilder("java", "Test1");
        //启动进程
        Process p = pb.start();
    }
}

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("被调用成功!");

    }
}

​ 以TestProcess.java文件执行成功后,我们会在c盘更目录下看见一个Test1.txt文件,但是控制台并没有输出 “被调用成功!”的信息。因此可以断定,Test已经被执行成功,但因为某种原因,Test1的输出信息未在TestProcess.java的控制台中输出。这个原因也很简单,因为使用start建立的是TestProcess的子进程,这个子进程并没有自己的控制台,因此,它并不会输出任何信息。

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

import java.io.*;

public class TestProcess {
	public static void main(String[] args) {
        //创建一个进程,调用Test1.java文件
        ProcessBuilder pb = new ProcessBuilder("java", "Test1");
        //启动进程
        Process p = pb.start();
        //获取子进程的输出流
        BufferedInputStream in = new BufferedInputStream(p.getInputStream());
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String s;
        while ((s = br.readLine()) != null){
        System.out.println(s);
        }
    }
}
//如上操作,就可在父进程中输出子进程的输出流 "被调用成功!"

2.2、Runtime示例

TestProcess.java文件

public class TestProcess {
	public static void main(String[] args) {
        //获取Runtime引用对象
        Runtime run = Runtime.getRuntime();
        //启动进程,Test1是类名称
        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);
        }
    }
}

​ 以上除了输出信息,还有输入信息。既然子进程没有自己的控制台,那么输入信息也得由父进程提供。我们可以通过Process的getOutputStream方法来为子进程提供输入信息(即由父进程向子进程输入信息,而不是由控制台输入信息)。我们可以看看如下的代码:

Test1.java文件代码

public class Test {
    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("由父进程输入的信息:" + br.readLine());
    }
}

TestProcess.java文件

public class TestProcess {
	public static void main(String[] args) {
        //获取Runtime引用对象
        Runtime run = Runtime.getRuntime();
        //启动进程
        Process p = run.exec("java Test1");
		//向子进程输入信息
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
        bw.write("向子进程输出信息");
        bw.flush();
        bw.close(); // 必须得关闭流,否则无法向子进程中输入信息
        
        //获取子进程输出流
        BufferedInputStream in = new BufferedInputStream(p.getInputStream());
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String s;
        while ((s = br.readLine()) != null) {
        	System.out.println(s);
        }
    }
}

​ 从以上代码可以看出,Test1得到由TestProcess发过来的信息,并将其输出。当你不加bw.flash()和bw.close()时,信息将无法到达子进程,也就是说子进程进入阻塞状态,但由于父进程已经退出了,因此,子进程也跟着退出了。如果要证明这一点,可以在最后加上System.in.read(),然后通过任务管理器(在windows下)查看java进程,你会发现如果加上bw.flush()和bw.close(),只有一个java进程存在,如果去掉它们,就有两个java进程存在。这是因为,如果将信息传给Test1,在得到信息后,Test1就退出了。在这里有一点需要说明一下,exec的执行是异步的,并不会因为执行的某个程序阻塞而停止执行下面的代码。因此,可以在运行Test1后,仍可以执行下面的代码。

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

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值