JAVA 多進程 概念 創建 通信

多進程的概念
  • 一個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:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章