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();
	}
}

运行结果:


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


猜你喜欢

转载自blog.csdn.net/huxiny/article/details/79900479