Java IO的管道流包括PipedOutputStream和PipedInputStream,即管道输出流和管道输入流,两者配合使用可以实现线程间通信。
典型的一种情况:一个线程将数据写入PipedOutputStream,另一个线程从PipedInputStream中读取数据。基本模型如下:
所以使用管道实现线程间通信的主要流程为:
1、建立PipedOutputStream和PipedInputStream,将两者连接。
2、提供数据,调用PipedOutputStream进行数据写入。
3、调用PipedInputStream进行数据读取。
示例:
package com.io.test;
import java.io.IOException;
import java.io.PipedInputStream;
/**
* 数据接收者,从管道输入流中读取数据
*/
public class PipedConsumer implements Runnable{
//输入流, 默认缓冲区大小为1024
private PipedInputStream in;
public void setPipedInputStream(PipedInputStream in){
this.in = in;
}
@Override
public void run() {
readMessage();
}
public String readMessage(){
byte [] buf = new byte[1024];
try{
in.read(buf);
System.out.println("管道流中数据为: " + new String(buf, 0, buf.length));
}catch(IOException e){
e.printStackTrace();
}
return new String(buf, 0, buf.length);
}
}
package com.io.test;
import java.io.PipedOutputStream;
/**
* 数据传递者,将数据写入管道流
*/
public class PipedProducer implements Runnable{
private PipedOutputStream out;
public void setPipedOutputStream(PipedOutputStream out){
this.out = out;
}
@Override
public void run() {
writeMessage(null);
}
public void writeMessage(String message){
if(message == null){
message = "Hello World!";
}
try{
out.write(message.getBytes());
}catch(Exception e){
e.printStackTrace();
}
}
}
package com.io.test;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class TestPipedStream {
public static void main(String[] args) throws IOException{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream(in);
PipedProducer producer = new PipedProducer();
PipedConsumer consumer = new PipedConsumer();
consumer.setPipedInputStream(in);
producer.setPipedOutputStream(out);
//推荐使用不同线程进行数据写入和读取,使用同一个线程进行写入和读取,可能造成死锁
// Thread threadA = new Thread(producer);
// Thread threadB = new Thread(consumer);
// threadA.start();
// threadB.start();
//死锁案例
String message = "dead lock!";
while(message.length()>0){
producer.writeMessage(message);
String str = consumer.readMessage();
message = str;
}
}
}
问:
1、为什么写入PipedOutputStream的数据能从PipedInputStream读取?两者是如何连接起来的?由谁发起的连接?
答:PipedOutputStream内部有与其连接的PipedInputStream对象的引用,实际写入数据时,是直接让PipedInputStream接收数据,直接写入PipedInputStream对象内部的缓冲数组中。读取数据时,PipedInputStream直接从内部缓冲数组中读取。所以PipedOutputStream没有缓冲区,PipedInputStream有数据缓冲区。PipedOutputStream作为管道的数据发送端,连接均由其创建。
2、PipedInputStream内部缓冲的字节数组默认容量是1024,若数据较大,如何能存储下?
答:jdk1.8源码如下:
//接收数据字节,存到缓充数组中
protected synchronized void receive(int b) throws IOException {
checkStateForReceive(); //检查接收状态:未连接、已关闭、无线程读取数据,则不再接受
writeSide = Thread.currentThread(); //当前线程为写入侧
if (in == out) //下一个存储的位置和下一个读取的位置相同,表示所有数据已经被读取,等待
awaitSpace();
if (in < 0) { //下一个存储的位置<0,表示还没有数据进来,设置初始值
in = 0;
out = 0;
}
buffer[in++] = (byte)(b & 0xFF); //存储字节
if (in >= buffer.length) { //字节数组存满了数据,从头开始存,会覆盖已有的字节数据
in = 0;
}
}