Java高并发之线程通信(八)---管道

Java高并发之线程通信(管道)

Java管道原理:

广义上讲,管道就是一个用来在两个实体之间单项数据传输的导管。Java中的管道和linux中的管道是一样的,从本质上说,管道也是一种文件。

实际上原理很简单,如下图所示:

管道流特点:

1. 管道数据流向的单向的,数据只能从一个进程(线程)流向另一个进程(线程),如果要进行双向通信,必须建立两个管道。

 

2. 管道的读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多数据。

 

3. 当管道输出流write()导致管道缓冲区变满时,管道的write()调用将默认的被阻塞,等待缓冲区的数据被读取。同样的读进程也可能工作得比写进程块。当所有当前进程数据被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认被阻塞,等待缓冲区数据,这解决了read()调用返回文件结束的问题。

 

4. 管道输出流或者管道输入流的提前关闭,不会影响到对端流。比如输出流提前结束,输入流不会产生异常;输入流的提前结束也不会影响到输出流。

管道使用实例:

注意:管道输出流输出结束后一定要关闭输出流,因为只有关闭输出流之后,对端输入流read()才能返回null,如果没有关闭输出流会发生什么呢?

当输出流使用后关闭时:

public class test {
    public static class Write extends Thread{
        public PipedOutputStream pos;
        Write(PipedOutputStream pos){
            this.pos = pos;
        }
        public void run(){
            PrintStream p = new PrintStream(pos);
            for(int i=1;i<10;i++){
                p.println("hello");
                p.flush();
            }
            p.close();
        }
    }
    
    public static class Read extends Thread{
        public PipedInputStream pis;
        public String line = "null";
        Read(PipedInputStream pis){
            this.pis = pis;
        }
        public void run(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println(getName()+": "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }    
 
    public static void main(String args[]) throws InterruptedException, IOException{
        //创建管道通信流
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream(pos);
        new Write(pos).start();
        new Read(pis).start();
    }
}


输出结果如下:

可以看到最后管道输入流返回的是null。

 

当输出流使用完后不关闭时:

public class test {
    public static class Write extends Thread{
        public PipedOutputStream pos;
        Write(PipedOutputStream pos){
            this.pos = pos;
        }
        public void run(){
            PrintStream p = new PrintStream(pos);
            for(int i=1;i<10;i++){
                p.println("hello");
                p.flush();
            }
            //p.close();
        }
    }
    
    public static class Read extends Thread{
        public PipedInputStream pis;
        public String line = "null";
        Read(PipedInputStream pis){
            this.pis = pis;
        }
        public void run(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println(getName()+": "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }    
 
    public static void main(String args[]) throws InterruptedException, IOException{
        //创建管道通信流
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream(pos);
        new Write(pos).start();
        new Read(pis).start();
    }
}


输出结果如下:

可以看到输入流没有返回null,而是返回一个异常:Write end dead。

所以当管道通信结束后一定要关闭管道输出流。输入流可以通过read()方法是否返回null来判断管道通信是否结束。


工程中的管道使用:

上述的管道通信的例子只是用于实验的,在实际工程中我们并不会这样使用管道,把管道的两端(即管道输出流和管道输入流)设置为全局可见的是不安全的,会造成管道泄漏的问题。

我们看下面的代码:


public class test {
    public static class Write extends Thread{
        public PipedOutputStream pos;
        Write(PipedOutputStream pos){
            this.pos = pos;
        }
        public void run(){
            PrintStream p = new PrintStream(pos);
            for(int i=1;i<1000;i++){
                p.println("hello");
                p.flush();
            }
            p.close();
        }
    }
    
    public static class Read extends Thread{
        public PipedInputStream pis;
        public String line = "null";
        Read(PipedInputStream pis){
            this.pis = pis;
        }
        public void run(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println("read: "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static class Other_Thread extends Thread{
        public PipedInputStream pis;
        public String line = "null";
        Other_Thread(PipedInputStream pis){
            this.pis = pis;
        }
        public void run(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println("Other_Thread: "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    public static void main(String args[]) throws InterruptedException, IOException{
        //创建管道通信流
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream(pos);
        new Write(pos).start();
        new Read(pis).start();
        new Other_Thread(pis).start();
    }
}


这里我多创建了一个线程Other_Thread,用来模拟write线程和read线程正常通信的情况下,Other_Thread线程获取了全局的管道输入流,并运行,看看会发生什么吧。

运行结果:

这是运行结果的一个片段,但是已经可以说明问题了,write线程的管道数据被Other_Thread线程截取了,这样的设计会产生管道泄漏问题。

在工程中更科学的利用管道应该如下:

public class test {
    public static class Write extends Thread{
        public PipedOutputStream pos = null;
        
                //获取线程中的管道输出流
        public PipedOutputStream getPos(){
                        pos = new PipedOutputStream();
            return pos;
        }
        @Override
        public void run(){
            PrintStream p = new PrintStream(pos);
            for(int i=1;i<1000;i++){
                p.println("hello");
                p.flush();
            }
            p.close();
        }
    }
    
    public static class Read extends Thread{
        public PipedInputStream pis = null; 
        public String line = "null";
 
        //获得线程中的管道输入流
        public PipedInputStream getPis(){
                        pis = new PipedInputStream();
            return pis;
        }
        @Override
        public void run(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println("read: "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String args[]) throws InterruptedException, IOException{
        Write write = new Write();
        Read read = new Read();
        //连接两个线程的管道流
        write.getPos().connect(read.getPis());
        write.start();
        read.start();
    }
}

 


在实际工程中一个线程往往需要跟多个线程通信,比如,write线程与read线程通信结束后,还需要write线程与其他线程通信,该怎么办呢?请看如下代码:

public class test {
    public static class Write extends Thread{
        public PipedOutputStream pos = null;
        
        //获取线程中的管道输出流
        public PipedOutputStream getPos(){
            pos = new PipedOutputStream();
            return pos;
        }
        //把数据通过管道输出流发送出去
        public void SentData(){
            PrintStream p = new PrintStream(pos);
            for(int i=1;i<10;i++){
                p.println("hello");
                p.flush();
            }
            p.close();
        }
        @Override
        public void run(){
            while(true);         //模拟耗时工作
        }
    }
    
    public static class Read extends Thread{
        public PipedInputStream pis = null; 
        public String line = "null";
        
        //获得线程中的管道输入流
        public PipedInputStream getPis(){
            pis = new PipedInputStream();
            return pis;
        }
        //利用管道输入流接收管道数据
        public void ReceiveData(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println("read: "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run(){
            while(true);        //模拟耗时工作
        }
    }
    
    public static class Other_Thread extends Thread{
        public PipedInputStream pis = null; 
        public String line = "null";
        
        //获得线程中的管道输入流
        public PipedInputStream getPis(){
            pis = new PipedInputStream();
            return pis;
        }
        //利用管道输入流接收管道数据
        public void ReceiveData(){
            BufferedReader r = new BufferedReader(new InputStreamReader(pis));
            try {
                while(line!=null){
                    line = r.readLine();
                    System.out.println("Other thread: "+line);
                }
                r.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run(){
            while(true);   //模拟耗时操作
        }
    }
    public static void main(String args[]) throws InterruptedException, IOException{
        Write write = new Write();
        Read read = new Read();
        Other_Thread other = new Other_Thread();
        //连接两个线程的管道流 ---read和write线程
        write.getPos().connect(read.getPis());
        write.start();
        read.start();
        other.start();
        write.SentData();
        read.ReceiveData();
        Thread.sleep(2000);
        //重新连接两个线程的管道流 ---Other_Thread和write线程
        write.getPos().connect(other.getPis());
        write.SentData();
        other.ReceiveData();
    }
}


运行结果:

这只是输出的一个片段,但是已经能够说明问题了。


--------------------- 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值