本章,我们对java 管道进行学习。
java 管道介绍
在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流。
它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。
PipedOutputStream和PipedInputStream源码分析
下面介绍PipedOutputStream和PipedInputStream的源码。在阅读它们的源码之前,建议先看看源码后面的示例。待理解管道的作用和用法之后,再看源码,可能更容易理解。
此外,由于在“java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)”中已经对PipedOutputStream的父类OutputStream进行了介绍,这里就不再介绍OutputStream。
在“java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)”中已经对PipedInputStream的父类InputStream进行了介绍,这里也不再介绍InputStream。
1. PipedOutputStream 源码分析(基于jdk1.7.40)
1 packagejava.io;2
3 import java.io.*;4
5 public class PipedOutputStream extendsOutputStream {6
7 //与PipedOutputStream通信的PipedInputStream对象
8 privatePipedInputStream sink;9
10 //构造函数,指定配对的PipedInputStream
11 public PipedOutputStream(PipedInputStream snk) throwsIOException {12 connect(snk);13 }14
15 //构造函数
16 publicPipedOutputStream() {17 }18
19 //将“管道输出流” 和 “管道输入流”连接。
20 public synchronized void connect(PipedInputStream snk) throwsIOException {21 if (snk == null) {22 throw newNullPointerException();23 } else if (sink != null ||snk.connected) {24 throw new IOException("Already connected");25 }26 //设置“管道输入流”
27 sink =snk;28 //初始化“管道输入流”的读写位置29 //int是PipedInputStream中定义的,代表“管道输入流”的读写位置
30 snk.in = -1;31 //初始化“管道输出流”的读写位置。32 //out是PipedInputStream中定义的,代表“管道输出流”的读写位置
33 snk.out = 0;34 //设置“管道输入流”和“管道输出流”为已连接状态35 //connected是PipedInputStream中定义的,用于表示“管道输入流与管道输出流”是否已经连接
36 snk.connected = true;37 }38
39 //将int类型b写入“管道输出流”中。40 //将b写入“管道输出流”之后,它会将b传输给“管道输入流”
41 public void write(int b) throwsIOException {42 if (sink == null) {43 throw new IOException("Pipe not connected");44 }45 sink.receive(b);46 }47
48 //将字节数组b写入“管道输出流”中。49 //将数组b写入“管道输出流”之后,它会将其传输给“管道输入流”
50 public void write(byte b[], int off, int len) throwsIOException {51 if (sink == null) {52 throw new IOException("Pipe not connected");53 } else if (b == null) {54 throw newNullPointerException();55 } else if ((off < 0) || (off > b.length) || (len < 0) ||
56 ((off + len) > b.length) || ((off + len) < 0)) {57 throw newIndexOutOfBoundsException();58 } else if (len == 0) {59 return;60 }61 //“管道输入流”接收数据
62 sink.receive(b, off, len);63 }64
65 //清空“管道输出流”。66 //这里会调用“管道输入流”的notifyAll();67 //目的是让“管道输入流”放弃对当前资源的占有,让其它的等待线程(等待读取管道输出流的线程)读取“管道输出流”的值。
68 public synchronized void flush() throwsIOException {69 if (sink != null) {70 synchronized(sink) {71 sink.notifyAll();72 }73 }74 }75
76 //关闭“管道输出流”。77 //关闭之后,会调用receivedLast()通知“管道输入流”它已经关闭。
78 public void close() throwsIOException {79 if (sink != null) {80 sink.receivedLast();81 }82 }83 }
View Code
2. PipedInputStream 源码分析(基于jdk1.7.40)
1 packagejava.io;2
3 public class PipedInputStream extendsInputStream {4 //“管道输出流”是否关闭的标记
5 boolean closedByWriter = false;6 //“管道输入流”是否关闭的标记
7 volatile boolean closedByReader = false;8 //“管道输入流”与“管道输出流”是否连接的标记9 //它在PipedOutputStream的connect()连接函数中被设置为true
10 boolean connected = false;11
12 Thread readSide; //读取“管道”数据的线程
13 Thread writeSide; //向“管道”写入数据的线程14
15 //“管道”的默认大小
16 private static final int DEFAULT_PIPE_SIZE = 1024;17
18 protected static final int PIPE_SIZE =DEFAULT_PIPE_SIZE;19
20 //缓冲区
21 protected bytebuffer[];22
23 //下一个写入字节的位置。in==out代表满,说明“写入的数据”全部被读取了。
24 protected int in = -1;25 //下一个读取字节的位置。in==out代表满,说明“写入的数据”全部被读取了。
26 protected int out = 0;27
28 //构造函数:指定与“管道输入流”关联的“管道输出流”
29 public PipedInputStream(PipedOutputStream src) throwsIOException {30 this(src, DEFAULT_PIPE_SIZE);31 }32
33 //构造函数:指定与“管道输入流”关联的“管道输出流”,以及“缓冲区大小”
34 public PipedInputStream(PipedOutputStream src, intpipeSize)35 throwsIOException {36 initPipe(pipeSize);37 connect(src);38 }39
40 //构造函数:默认缓冲区大小是1024字节
41 publicPipedInputStream() {42 initPipe(DEFAULT_PIPE_SIZE);43 }44
45 //构造函数:指定缓冲区大小是pipeSize
46 public PipedInputStream(intpipeSize) {47 initPipe(pipeSize);48 }49
50 //初始化“管道”:新建缓冲区大小
51 private void initPipe(intpipeSize) {52 if (pipeSize <= 0) {53 throw new IllegalArgumentException("Pipe Size <= 0");54 }55 buffer = new byte[pipeSize];56 }57
58 //将“管道输入流”和“管道输出流”绑定。59 //实际上,这里调用的是PipedOutputStream的connect()函数
60 public void connect(PipedOutputStream src) throwsIOException {61 src.connect(this);62 }63
64 //接收int类型的数据b。65 //它只会在PipedOutputStream的write(int b)中会被调用
66 protected synchronized void receive(int b) throwsIOException {67 //检查管道状态
68 checkStateForReceive();69 //获取“写入管道”的线程
70 writeSide =Thread.currentThread();71 //若“写入管道”的数据正好全部被读取完,则等待。
72 if (in ==out)73 awaitSpace();74 if (in < 0) {75 in = 0;76 out = 0;77 }78 //将b保存到缓冲区
79 buffer[in++] = (byte)(b & 0xFF);80 if (in >=buffer.length) {81 in = 0;82 }83 }84
85 //接收字节数组b。
86 synchronized void receive(byte b[], int off, int len) throwsIOException {87 //检查管道状态
88 checkStateForReceive();89 //获取“写入管道”的线程
90 writeSide =Thread.currentThread();91 int bytesToTransfer =len;92 while (bytesToTransfer > 0) {93 //若“写入管道”的数据正好全部被读取完,则等待。
94 if (in ==out)95 awaitSpace();96 int nextTransferAmount = 0;97 //如果“管道中被读取的数据,少于写入管道的数据”;98 //则设置nextTransferAmount=“buffer.length - in”
99 if (out
104 if (in == -1) {105 in = out = 0;106 nextTransferAmount = buffer.length -in;107 } else{108 nextTransferAmount = out -in;109 }110 }111 if (nextTransferAmount >bytesToTransfer)112 nextTransferAmount =bytesToTransfer;113 //assert断言的作用是,若nextTransferAmount <= 0,则终止程序。
114 assert(nextTransferAmount > 0);115 //将数据写入到缓冲中
116 System.arraycopy(b, off, buffer, in, nextTransferAmount);117 bytesToTransfer -=nextTransferAmount;118 off +=nextTransferAmount;119 in +=nextTransferAmount;120 if (in >=buffer.length) {121 in = 0;122 }123 }124 }125
126 //检查管道状态
127 private void checkStateForReceive() throwsIOException {128 if (!connected) {129 throw new IOException("Pipe not connected");130 } else if (closedByWriter ||closedByReader) {131 throw new IOException("Pipe closed");132 } else if (readSide != null && !readSide.isAlive()) {133 throw new IOException("Read end dead");134 }135 }136
137 //等待。138 //若“写入管道”的数据正好全部被读取完(例如,管道缓冲满),则执行awaitSpace()操作;139 //它的目的是让“读取管道的线程”管道产生读取数据请求,从而才能继续的向“管道”中写入数据。
140 private void awaitSpace() throwsIOException {141
142 //如果“管道中被读取的数据,等于写入管道的数据”时,143 //则每隔1000ms检查“管道状态”,并唤醒管道操作:若有“读取管道数据线程被阻塞”,则唤醒该线程。
144 while (in ==out) {145 checkStateForReceive();146
147 /*full: kick any waiting readers*/
148 notifyAll();149 try{150 wait(1000);151 } catch(InterruptedException ex) {152 throw newjava.io.InterruptedIOException();153 }154 }155 }156
157 //当PipedOutputStream被关闭时,被调用
158 synchronized voidreceivedLast() {159 closedByWriter = true;160 notifyAll();161 }162
163 //从管道(的缓冲)中读取一个字节,并将其转换成int类型
164 public synchronized int read() throwsIOException {165 if (!connected) {166 throw new IOException("Pipe not connected");167 } else if(closedByReader) {168 throw new IOException("Pipe closed");169 } else if (writeSide != null && !writeSide.isAlive()170 && !closedByWriter && (in < 0)) {171 throw new IOException("Write end dead");172 }173
174 readSide =Thread.currentThread();175 int trials = 2;176 while (in < 0) {177 if(closedByWriter) {178 /*closed by writer, return EOF*/
179 return -1;180 }181 if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {182 throw new IOException("Pipe broken");183 }184 /*might be a writer waiting*/
185 notifyAll();186 try{187 wait(1000);188 } catch(InterruptedException ex) {189 throw newjava.io.InterruptedIOException();190 }191 }192 int ret = buffer[out++] & 0xFF;193 if (out >=buffer.length) {194 out = 0;195 }196 if (in ==out) {197 /*now empty*/
198 in = -1;199 }200
201 returnret;202 }203
204 //从管道(的缓冲)中读取数据,并将其存入到数组b中
205 public synchronized int read(byte b[], int off, int len) throwsIOException {206 if (b == null) {207 throw newNullPointerException();208 } else if (off < 0 || len < 0 || len > b.length -off) {209 throw newIndexOutOfBoundsException();210 } else if (len == 0) {211 return 0;212 }213
214 /*possibly wait on the first character*/
215 int c =read();216 if (c < 0) {217 return -1;218 }219 b[off] = (byte) c;220 int rlen = 1;221 while ((in >= 0) && (len > 1)) {222
223 intavailable;224
225 if (in >out) {226 available = Math.min((buffer.length - out), (in -out));227 } else{228 available = buffer.length -out;229 }230
231 //A byte is read beforehand outside the loop
232 if (available > (len - 1)) {233 available = len - 1;234 }235 System.arraycopy(buffer, out, b, off +rlen, available);236 out +=available;237 rlen +=available;238 len -=available;239
240 if (out >=buffer.length) {241 out = 0;242 }243 if (in ==out) {244 /*now empty*/
245 in = -1;246 }247 }248 returnrlen;249 }250
251 //返回不受阻塞地从此输入流中读取的字节数。
252 public synchronized int available() throwsIOException {253 if(in < 0)254 return 0;255 else if(in ==out)256 returnbuffer.length;257 else if (in >out)258 return in -out;259 else
260 return in + buffer.length -out;261 }262
263 //关闭管道输入流
264 public void close() throwsIOException {265 closedByReader = true;266 synchronized (this) {267 in = -1;268 }269 }270 }
View Code
管道通信示例
下面,我们看看多线程中通过管道通信的例子。例子中包括3个类:Receiver.java, PipedStreamTest.java 和 Sender.java。
Receiver.java的代码如下:
1 importjava.io.IOException;2
3 importjava.io.PipedInputStream;4
5 @SuppressWarnings("all")6 /**
7 * 接收者线程8 */
9 public class Receiver extendsThread {10
11 //管道输入流对象。12 //它和“管道输出流(PipedOutputStream)”对象绑定,13 //从而可以接收“管道输出流”的数据,再让用户读取。
14 private PipedInputStream in = newPipedInputStream();15
16 //获得“管道输入流”对象
17 publicPipedInputStream getInputStream(){18 returnin;19 }20
21 @Override22 public voidrun(){23 readMessageOnce() ;24 //readMessageContinued() ;
25 }26
27 //从“管道输入流”中读取1次数据
28 public voidreadMessageOnce(){29 //虽然buf的大小是2048个字节,但最多只会从“管道输入流”中读取1024个字节。30 //因为,“管道输入流”的缓冲区大小默认只有1024个字节。
31 byte[] buf = new byte[2048];32 try{33 int len =in.read(buf);34 System.out.println(new String(buf,0,len));35 in.close();36 } catch(IOException e) {37 e.printStackTrace();38 }39 }40 //从“管道输入流”读取>1024个字节时,就停止读取
41 public voidreadMessageContinued() {42 int total=0;43 while(true) {44 byte[] buf = new byte[1024];45 try{46 int len =in.read(buf);47 total +=len;48 System.out.println(new String(buf,0,len));49 //若读取的字节总数>1024,则退出循环。
50 if (total > 1024)51 break;52 } catch(IOException e) {53 e.printStackTrace();54 }55 }56
57 try{58 in.close();59 } catch(IOException e) {60 e.printStackTrace();61 }62 }63 }
View Code
Sender.java的代码如下:
1 importjava.io.IOException;2
3 importjava.io.PipedOutputStream;4 @SuppressWarnings("all")5 /**
6 * 发送者线程7 */
8 public class Sender extendsThread {9
10 //管道输出流对象。11 //它和“管道输入流(PipedInputStream)”对象绑定,12 //从而可以将数据发送给“管道输入流”的数据,然后用户可以从“管道输入流”读取数据。
13 private PipedOutputStream out = newPipedOutputStream();14
15 //获得“管道输出流”对象
16 publicPipedOutputStream getOutputStream(){17 returnout;18 }19
20 @Override21 public voidrun(){22 writeShortMessage();23 //writeLongMessage();
24 }25
26 //向“管道输出流”中写入一则较简短的消息:"this is a short message"
27 private voidwriteShortMessage() {28 String strInfo = "this is a short message";29 try{30 out.write(strInfo.getBytes());31 out.close();32 } catch(IOException e) {33 e.printStackTrace();34 }35 }36 //向“管道输出流”中写入一则较长的消息
37 private voidwriteLongMessage() {38 StringBuilder sb = newStringBuilder();39 //通过for循环写入1020个字节
40 for (int i=0; i<102; i++)41 sb.append("0123456789");42 //再写入26个字节。
43 sb.append("abcdefghijklmnopqrstuvwxyz");44 //str的总长度是1020+26=1046个字节
45 String str =sb.toString();46 try{47 //将1046个字节写入到“管道输出流”中
48 out.write(str.getBytes());49 out.close();50 } catch(IOException e) {51 e.printStackTrace();52 }53 }54 }
View Code
PipedStreamTest.java的代码如下:
1 importjava.io.PipedInputStream;2 importjava.io.PipedOutputStream;3 importjava.io.IOException;4
5 @SuppressWarnings("all")6 /**
7 * 管道输入流和管道输出流的交互程序8 */
9 public classPipedStreamTest {10
11 public static voidmain(String[] args) {12 Sender t1 = newSender();13
14 Receiver t2 = newReceiver();15
16 PipedOutputStream out =t1.getOutputStream();17
18 PipedInputStream in =t2.getInputStream();19
20 try{21 //管道连接。下面2句话的本质是一样。22 //out.connect(in);
23 in.connect(out);24
25 /**
26 * Thread类的START方法:27 * 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。28 * 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。29 * 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。30 */
31 t1.start();32 t2.start();33 } catch(IOException e) {34 e.printStackTrace();35 }36 }37 }
View Code
运行结果:
this is a short message
说明:
(01)
in.connect(out);
将“管道输入流”和“管道输出流”关联起来。查看PipedOutputStream.java和PipedInputStream.java中connect()的源码;我们知道 out.connect(in); 等价于 in.connect(out);
(02)
t1.start(); // 启动“Sender”线程
t2.start(); // 启动“Receiver”线程
先查看Sender.java的源码,线程启动后执行run()函数;在Sender.java的run()中,调用writeShortMessage();
writeShortMessage();的作用就是向“管道输出流”中写入数据"this is a short message" ;这条数据会被“管道输入流”接收到。下面看看这是如何实现的。
先看write(byte b[])的源码,在OutputStream.java中定义。PipedOutputStream.java继承于OutputStream.java;OutputStream.java中write(byte b[])的源码如下:
public void write(byte b[]) throwsIOException {
write(b,0, b.length);
}
实际上write(byte b[])是调用的PipedOutputStream.java中的write(byte b[], int off, int len)函数。查看write(byte b[], int off, int len)的源码,我们发现:它会调用 sink.receive(b, off, len); 进一步查看receive(byte b[], int off, int len)的定义,我们知道sink.receive(b, off, len)的作用就是:将“管道输出流”中的数据保存到“管道输入流”的缓冲中。而“管道输入流”的缓冲区buffer的默认大小是1024个字节。
至此,我们知道:t1.start()启动Sender线程,而Sender线程会将数据"this is a short message"写入到“管道输出流”;而“管道输出流”又会将该数据传输给“管道输入流”,即而保存在“管道输入流”的缓冲中。
接下来,我们看看“用户如何从‘管道输入流’的缓冲中读取数据”。这实际上就是Receiver线程的动作。
t2.start() 会启动Receiver线程,从而执行Receiver.java的run()函数。查看Receiver.java的源码,我们知道run()调用了readMessageOnce()。
而readMessageOnce()就是调用in.read(buf)从“管道输入流in”中读取数据,并保存到buf中。
通过上面的分析,我们已经知道“管道输入流in”的缓冲中的数据是"this is a short message";因此,buf的数据就是"this is a short message"。
为了加深对管道的理解。我们接着进行下面两个小试验。
试验一:修改Sender.java
将
public voidrun(){
writeShortMessage();//writeLongMessage();
}
修改为
public voidrun(){//writeShortMessage();
writeLongMessage();
}
运行程序。运行结果为:
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
012345678901234567890123456789abcd
这些数据是通过writeLongMessage()写入到“管道输出流”,然后传送给“管道输入流”,进而存储在“管道输入流”的缓冲中;再被用户从缓冲读取出来的数据。
然后,观察writeLongMessage()的源码。我们可以发现,str的长度是1046个字节,然后运行结果只有1024个字节!为什么会这样呢?
道理很简单:管道输入流的缓冲区默认大小是1024个字节。所以,最多只能写入1024个字节。
观察PipedInputStream.java的源码,我们能了解的更透彻。
private static final int DEFAULT_PIPE_SIZE = 1024;publicPipedInputStream() {
initPipe(DEFAULT_PIPE_SIZE);
}
默认构造函数调用initPipe(DEFAULT_PIPE_SIZE),它的源码如下:
private void initPipe(intpipeSize) {if (pipeSize <= 0) {throw new IllegalArgumentException("Pipe Size <= 0");
}
buffer= new byte[pipeSize];
}
从中,我们可以知道缓冲区buffer的默认大小就是1024个字节。
试验二: 在“试验一”的基础上继续修改Receiver.java
将
public voidrun(){
readMessageOnce() ;//readMessageContinued() ;
}
修改为
public voidrun(){//readMessageOnce() ;
readMessageContinued() ;
}
运行程序。运行结果为:
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
012345678901234567890123456789abcd
efghijklmnopqrstuvwxyz
这个结果才是writeLongMessage()写入到“输入缓冲区”的完整数据。
更多内容