JAVA 多进程 概念 创建 通信

版权声明:本文为博主原创文章,请尊重个人劳动成果,转载注明地址:http://blog.csdn.net/evan_man 谢谢! https://blog.csdn.net/evan_man/article/details/50808589
多进程的概念
  • 一个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:


猜你喜欢

转载自blog.csdn.net/evan_man/article/details/50808589
今日推荐