多进程的概念
- 一个JVM进程对应一个JAVA程序
- Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。
java创建多进程的方法有下面两种
- 法一:
- Runtime rt = Runtime.getRuntime();
- Process process = rt.exec("java com.test.process.MyClass");
- 法二:
- ProcessBuilder pb = new ProcessBuilder(myXXcommand,myXXobject);
- Process p = pb.start();
- 注意:
- 建议使用法二进行创建,因为相对于法一这里提供了更多的可以设置的东西,如环境变量,工作目录等;
- 关于创建ProcessBuilder对象是所使用的字符串command的含义:
- command指令其实就是指在当前操作系统中如何启动一个应用的指令;比如下面的指令
String myQQcommand = "E:\\Program Files (x86)\\Tencent\\QQ\\Bin\\QQ.exe"; //启动windows下面的qq程序 String myNotePadcommand = "E:\\Program Files\\Notepad++\\notepad++.exe"; //启动windows下的notepad程序 String myNotePadobject= "C:\\Users\\evan\\Desktop\\files.txt";//notepad程序用于打开的应用 String myJavacommand = "java";//运行java指令 String myJavaobject = "com.test.process.MyClass";//指定Java运行的类对象
- 环境:是从变量到值的依赖于系统的映射。初始值是当前进程环境的一个副本(请参阅 System.getenv())。
- 工作目录:默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名。
- redirectErrorStream 属性:最初,此属性为 false,意思是子进程的标准输出和错误输出被发送给两个独立的流,这些流可以通过 Process.getInputStream() 和 Process.getErrorStream() 方法来访问。如果将值设置为 true,标准错误将与标准输出合并。这使得关联错误消息和相应的输出变得更容易。在此情况下,合并的数据可从 Process.getInputStream() 返回的流读取,而从 Process.getErrorStream() 返回的流读取将直接到达文件尾
- 进程间通信的方法有:
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 父进程实现:
ServerSocket s = new ServerSocket(int port); Socket incoming = s.accept(); InputStream inStream = incoming.getInputStream(); OutputStream outStream = incoming.getOutputStream(); ...//读取操作,一般还会对InputStream和OutputStream分别用Scanner和PrintWriter进行一下包装 incoming.close();
- 子进程实现
Socket s = new Socket("127.0.0.1"",port); InputStream inStream = s.getInputStream(); OutputStream outStream = s.getOutputStream(); ....//读取操作,一般还会对InputStream和OutputStream分别用Scanner和PrintWriter进行一下包装
- 父进程实现:
- 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 管道方式(JAVA多进程间使用最简单)
- 父进程获取子进程输出流方式
BufferedInputStream in = new BufferedInputStream(p.getInputStream()); //p是前面创建的子进程 BufferedReader br = new BufferedReader(new InputStreamReader(in)); String s; while ((s = br.readLine()) != null) { System.out.println(s); }
- 父进程获取子进程输入流方式
- OutputStream ops = p.getOutputStream();
- ops.write(b);
- 子进程获取父进程输入输出流方式
- 子进程通过System.in得到的数据并不是从控制台输入的数据,而是父进程通过一个outputStream写入的数据;
- 子进程通过System.out输出的数据并不是输出到控制台,而是被父进程通过一个intputStream读取;
package com.test.process; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class T3 { public static void main(String[] args) throws IOException { System.out.println("子进程被调用成功!"); BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); while (true) { String strLine = bfr.readLine(); if (strLine != null) { System.out.println("hi:" + strLine); } } } }
- 管道通信方式总结如下:
- 父进程通过Process.getOutputStream()和Process.getInputStream()来获得子进程的输入输出流;
- 子进程通过System.in、System.out来获取父进程的输入输出流;
- 父进程获取子进程输出流方式
父进程测试类:
package com.test.process.pipe; import java.io.IOException; public class ProcessTest { static void main(String[] args) throws IOException, InterruptedException { Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); StringBuilder sbuilder = new StringBuilder(); for(int k=0;k<1;k++){ sbuilder.append("hello"); } int outSize = 1; TestOut out[] = new TestOut[outSize]; for(int i=0;i<outSize;i++){ out[i] = new TestOut(p,sbuilder.toString().getBytes()); new Thread(out[i]).start(); } int inSize = 1; TestIn in[] = new TestIn[inSize]; for(int j=0;j<inSize;j++){ in[j] = new TestIn(p); new Thread(in[j]).start(); } } }
子进程测试类
package com.test.process.pipe; import java.io.BufferedReader; import java.io.InputStreamReader; public class MyTest { public static void main(String[] args) throws Exception { //读取父进程输入流 BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); while (true) { String strLine = bfr.readLine(); if (strLine != null) { System.out.println(strLine);//这个地方的输出在子进程控制台是无法输出的,只可以在父进程获取子进程的输出 }else { return; } } } }
TestIn类
package com.test.process.pipe; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; public class TestIn implements Runnable{ private Process p = null; public TestIn(Process process){ p = process; } @Override public void run() { try { InputStream in = p.getInputStream(); BufferedReader bfr = new BufferedReader(new InputStreamReader(in)); String rd = bfr.readLine(); if(rd != null){ System.out.println(rd);//输出子进程返回信息(即子进程中的System.out.println()内容) }else{ return; } //注意这个地方,如果关闭流则子进程的返回信息无法获取 //bfr.close(); //in.close(); } catch (Exception e) { e.printStackTrace(); } } }
TestOut类
package com.test.process.pipe; import java.io.IOException; import java.io.OutputStream; public class TestOut implements Runnable { private Process p = null; private byte []b = null; public TestOut(Process process,byte byt[]){ p = process; b = byt; } @Override public void run() { try { OutputStream ops = p.getOutputStream(); //System.out.println("out--"+b.length); ops.write(b); //注意这个地方如果关闭,则父进程只可以给子进程发送一次信息,如果这个地方开启close()则父进程给子进程不管发送大小多大的数据,子进程都可以返回 //如果这个地方close()不开启,则父进程给子进程发送数据累加到8192子进程才返回。 //ops.close(); } catch (IOException e) { e.printStackTrace(); } } }
备注:
1、子进程的输出内容是无法在控制台输出的,只能再父类中获取并输出。2、父进程往子进程写内容时如果关闭字节流,则子进程的输入流同时关闭。
3、如果父进程中输入、输出流都不关闭,子进程获取的字节流在达到8129byte时才返回。
4、关闭子进程一定要在父进程中关闭 p.destroy()
Test1:
/** *如下另一种情况说明 *如果像如下情况执行会出现说明情况呢 *前提说明:TestOut类中开启ops.close(); */ package com.test.process.pipe; import java.io.IOException; public class ProcessTest { public static void main(String[] args) throws IOException, InterruptedException { Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); TestOut out = new TestOut(p,"Hello everyone".getBytes()); new Thread(out).start(); TestIn ti = new TestIn(p); new Thread(ti).start(); Thread.sleep(3000); TestOut out2 = new TestOut(p,"-Hello-everyone".getBytes()); new Thread(out2).start(); TestIn ti2 = new TestIn(p); new Thread(ti2).start(); }执行后输出结果为:
Hello everyone java.io.IOException: Stream closed at java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:145 ) at java.io.BufferedInputStream.read(BufferedInputStream.java:308) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) at java.io.InputStreamReader.read(InputStreamReader.java:167) at java.io.BufferedReader.fill(BufferedReader.java:136) at java.io.BufferedReader.readLine(BufferedReader.java:299) at java.io.BufferedReader.readLine(BufferedReader.java:362) at com.test.process.pipe.TestIn.run(TestIn.java:20) at java.lang.Thread.run(Thread.java:662)由此可见当创建一个子进程后,p.getOutputStream();p.getInputStream();通过两种方式使父进程与子进程建立管道连接,而当close()连接时管道关闭,再通过调用 p.getOutputStream();p.getInputStream();时直接出现IOException,结论为当父子进程建立连接后,通过管道长连接的方式进程信息传输,当close时在通过获取子进程的输入输出流
都会出现IOException;
Test2
对Test1中代码部分进行了修改
package com.test.process.pipe; import java.io.IOException; public class ProcessTest { public static void main(String[] args) throws IOException, InterruptedException { Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); TestOut out = new TestOut(p,"Hello everyone".getBytes()); new Thread(out).start(); TestIn ti = new TestIn(p); new Thread(ti).start(); Process p2 = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); TestOut out2 = new TestOut(p2,"-Hello-everyone".getBytes()); new Thread(out2).start(); TestIn ti2 = new TestIn(p2); new Thread(ti2).start(); }输出结果:
Hello everyone -Hello-everyone综上可见每个父进程创建一个子进程后,通过p.getOutputStream();p.getInputStream();建立管道连接后,无法关闭流,如果关闭了则需要重新建立进程才可以达到通信的效果。 如果不关闭流,则传输的字符内容累加到8192byte时才可以返回。
关于这里说的8192,参考如下源码可知:
BufferedReader类中
private static int defaultCharBufferSize = 8192;//默认字符数组长度
reference: