18、PipedReader和PipedWriter的源码分析和使用方法详细分析(windows操作系统,JDK8)
和PipedInputStream和PipedOutputStream一样,Java IO包中的PipedWriter和PipedReader也提供了一种高效的线程间通信机制,允许一批(多个)线程向PipedWriter写入数据,另一批(多个)线程从PipedReader读取数据。
但是,同一批(多个)线程相互之间会存在竞争,比如,同一批向PipedWriter写入数据的线程会存在竞争,同一批从PipedReader读取数据的线程也会存在竞争。因此PipedWriter和PipedReader中的线程安全需要通过synchronized关键字和wait()/notifyAll()机制实现。不建议在一个线程中同时使用PipedWriter和PipedReader,因为这样可能会导致这个线程陷入死锁状态。
PipedWriter和PipedReader之间的通信本质上也是一个生产者-消费者模型,其中PipedWriter作为生产者,PipedReader作为消费者。两者通过一个循环缓冲区(char[]数组)进行数据交换,PipedWriter将数据缓存在PipedReader的数组当中,等待PipedReader的读取。
PipedWriter和PipedReader的UML关系图,如下所示:

一、PipedWriter(生产者)源码——向PipedReader(消费者)中的缓冲区(char[]数组)写入字符数据的字符输出流(生产者)
package java.io;
public class PipedWriter extends Writer {
//与这个PipedWriter(生产者)相关联的 PipedReader (消费者)
private PipedReader sink;
//标记当前这个PipedWriter对象是否关闭,true表示关闭,false表示开启
private boolean closed = false;
//构造函数
public PipedWriter(PipedReader snk) throws IOException {
connect(snk);
}
//构造函数
public PipedWriter() {
}
//线程同步函数:用来改变将要关联的PipedReader (消费者)中一些变量的值
public synchronized void connect(PipedReader snk) throws IOException {
if (snk == null) {
throw new NullPointerException();//如果将要关联的PipedReader (消费者)为null,抛出NullPointerException
} else if (sink != null || snk.connected) {
//如果与这个PipedWriter(生产者)相关联的 PipedReader (消费者)!=null或者将要关联的PipedReader (消费者)的boolean connected变量为true,则抛出IOException
throw new IOException("Already connected");
} else if (snk.closedByReader || closed) {
//如果将要关联的PipedReader (消费者)的boolean closedByReader变量为true或者当前这个PipedWriter对象已经关闭,则抛出IOException
throw new IOException("Pipe closed");
}
sink = snk;//将这个PipedWriter(生产者)与这个PipedReader (消费者)相关联
snk.in = -1;//改变PipedReader (消费者)中的变量int in=-1
snk.out = 0;//改变PipedReader (消费者)中的变量int out=0
snk.connected = true;//改变PipedReader (消费者)中的变量boolean connected=true,表示该PipedReader (消费者)已经关联了某个PipedWriter(生产者)了
}
//向与这个PipedWriter(生产者)相关联的 PipedReader (消费者)的缓冲区(char[]数组)写入1个字符
public void write(int c) throws IOException {
if (sink == null) {
//如果与这个PipedWriter(生产者)相关联的 PipedReader (消费者)== null,抛出IOException
throw new IOException("Pipe not connected");
}
sink.receive(c);//最终调用的是这个相关联的 PipedReader (消费者)的receive(int b)函数
}
//向与这个PipedWriter(生产者)相关联的 PipedReader (消费者)的缓冲区(char[]数组)写入char[]数组cbuf的[off,off+len)(左闭右开,不包括off+len)索引位置的字符
public void write(char cbuf[], int off, int len) throws IOException {
if (sink == null) {
//如果与这个PipedWriter(生产者)相关联的 PipedReader(消费者)== null,抛出IOException
throw new IOException("Pipe not connected");
} else if ((off | len | (off + len) | (cbuf.length - (off + len))) < 0) {//char[]数组cbuf的[off,off+len)(左闭右开)索引位置是否有越界的检查
throw new IndexOutOfBoundsException();//越界的话,抛出一个IndexOutOfBoundsException
}
//最终调用的是这个相关联的 PipedReader (消费者)的receive(byte b[], int off, int len)函数
sink.receive(cbuf, off, len);
}
//线程同步函数:使用notifyAll()函数唤醒所有与这个PipedWriter(生产者)相关联的 PipedReader (消费者)线程(这个消费者可以绑定1~n个线程)
public synchronized void flush() throws IOException {
if (sink != null) {
if (sink.closedByReader || closed) {
throw new IOException("Pipe closed");
}
synchronized (sink) {
sink.notifyAll();
}
}
}
//关闭这个PipedWriter(生产者),这个PipedWriter(生产者)不能再向与它相关联的PipedReader(消费者)中的缓冲区(char[]数组)写入字符数据
public void close() throws IOException {
closed = true;
if (sink != null) {
sink.receivedLast();
}
}
}
二、PipedReader(消费者)源码——从自己的缓冲区(char[]数组)读取字符数据的字符输入流(消费者)
package java.io;
public class PipedReader extends Reader {
//标记符:true表示与这个 PipedReader (消费者)相关联的PipedWriter(生产者)已经关闭,反之,反之
boolean closedByWriter = false;
//标记符:true表示当前这个 PipedReader (消费者)已经关闭了,反之,反之
boolean closedByReader = false;
//标记符:true表示与这个 PipedReader (消费者)相关联的PipedWriter(生产者)已经持有了这个PipedReader (消费者)对象(或者叫已经连接上了),反之,反之
boolean connected = false;
Thread readSide;//当前消费的线程
Thread writeSide;//当前生产者的线程
//默认的PipedReader (消费者)的缓冲区(char[]数组)的长度
private static final int DEFAULT_PIPE_SIZE = 1024;
//PipedReader (消费者)的缓冲区(char[]数组)
char buffer[];
//缓冲区(char[]数组)的写指针
int in = -1;
//缓冲区(char[]数组)的读指针
int out = 0;
//构造函数
public PipedReader(PipedWriter src) throws IOException {
this(src, DEFAULT_PIPE_SIZE);//缓冲区(char[]数组)的长度使用默认值1024
}
//构造函数
public PipedReader(PipedWriter src, int pipeSize) throws IOException {
initPipe(pipeSize);//缓冲区(char[]数组)的长度使用指定的长度
//最终还是调用PipedWriter(生产者)的connect()函数,并把自身对象this传递进去,然后在PipedWriter(生产者)的connect()函数中,改变自己的3个变量int in=-1、int out=0、boolean connected=true
connect(src);
}
//构造函数,缓冲区(char[]数组)的长度使用默认值1024
public PipedReader() {
initPipe(DEFAULT_PIPE_SIZE);
}
//构造函数,缓冲区(char[]数组)的长度使用指定的长度
public PipedReader(int pipeSize) {
initPipe(pipeSize);
}
//初始化缓冲区(char[]数组)
private void initPipe(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe size <= 0");
}
buffer = new char[pipeSize];
}
public void connect(PipedWriter src) throws IOException {
src.connect(this); //最终还是调用PipedWriter(生产者)的connect()函数,并把自身对象this传递进去,然后在PipedWriter(生产者)的connect()函数中,改变自己的3个变量int in=-1、int out=0、boolean connected=true
}
//线程同步函数:该函数只被PipedWriter(生产者)的write(int b)函数调用
synchronized void receive(int c) throws IOException {
//检查PipedReader (消费者)的状态
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByWriter || closedByReader) {
throw new IOException("Pipe closed");
} else if (readSide != null && !readSide.isAlive()) {
throw new IOException("Read end dead");
}
writeSide = Thread.currentThread();//当前执行该函数的线程,就是生产者线程
while (in == out) {
//如果缓冲区(char[]数组)的读指针==缓冲区(char[]数组)的写指针,唤醒所有消费者线程,自己这个生产者线程调用wait(1000)函数
if ((readSide != null) && !readSide.isAlive()) {
throw new IOException("Pipe broken");
}
/* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
if (in < 0) {
//缓冲区(char[]数组)的写指针<0时,设置缓冲区(char[]数组)的写指针=0,缓冲区(char[]数组)的读指针=0
in = 0;
out = 0;
}
buffer[in++] = (char) c;//向缓冲区的写指针位置写入1个字节
if (in >= buffer.length) {
in = 0;//如果缓冲区满了,设置缓冲区的写指针=0
}
}
//线程同步函数:该函数只被PipedWriter(生产者)的write(char cbuf[], int off, int len)函数调用
synchronized void receive(char c[], int off, int len) throws IOException {
//如果缓冲区足够大可以装下len个字符的话,不停地向缓冲区(char[]数组)中顺序写入char[]数组c的[off,off+len)(左闭右开,不包括off+len)索引位置的字符
while (--len >= 0) {
receive(c[off++]);
}
}
//关闭与这个 PipedReader (消费者)相关联的PipedWriter(生产者)
synchronized void receivedLast() {
closedByWriter = true;
notifyAll();//唤醒所有消费者线程
}
public synchronized int read() throws IOException {
if (!connected) {//检查标记符connected,如果为false,抛出IOException
throw new IOException("Pipe not connected");
} else if (closedByReader) {//检查标记符closedByReader,如果为true,抛出IOException
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
//检查当前这个PipedReader (消费者)对象中引用的生产者线程和生产者线程的状态,如果和标记符closedByWriter还有缓冲区(char[]数组)的写指针(in)不能对应的话,抛出一个IOException
throw new IOException("Write end dead");
}
readSide = Thread.currentThread();//当前执行该函数的线程,就是消费者线程
int trials = 2;//这是一个多次检测的策略变量,防止生产者线程没有关闭了与这个 PipedReader (消费者)相关联的PipedWriter(生产者)时便抛出IOException
//in=-1的情况有3种:
//①、生产者线程还没有向缓冲区(char[]数组)中写任何字符
//②、消费者线程从缓冲区(char[]数组)中读完字符(char)数据以后读指针(out)=写指针(in),那么,当前消费者线程会设置写指针(in)=-1
//③、消费者线程执行PipedReader 的close()函数后,关闭了这个PipedReader(消费者)
while (in < 0) {
if (closedByWriter) {
/* closed by writer, return EOF */
return -1;
}
if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
//多个消费者线程从缓冲区(char[]数组)中读的时候,并且前一个消费者线程已经把缓冲区(char[]数组)中写入的字符读完了,并且前一个线程设置了写指针(in)=-1,生产者线程也关闭了与这个 PipedReader (消费者)相关联的PipedWriter(生产者)时,抛出一个IOException
throw new IOException("Pipe broken");
}
/* might be a writer waiting */
notifyAll();//此处的目的是为了唤醒所有生产者线程
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
int ret = buffer[out++];//获取缓冲区(char[]数组)中读指针(out)索引位置的字符,并且将读指针(out)+1
if (out >= buffer.length) {
out = 0;//如果读指针(out)>=缓冲区(char[]数组)的长度,设置读指针(out)=0
}
if (in == out) {
/* now empty */
in = -1;//如果消费者线程从缓冲区(char[]数组)中读完字符(char)数据以后读指针(out)=写指针(in),那么,当前消费者线程会设置写指针(in)=-1
}
return ret;
}
//线程同步函数:如果缓冲区(char[]数组)中有足够多的字符的话(数量>len),消费者线程每次从缓冲区(char[]数组)中读取len个字符放到char[]数组cbuf的[off, off+len)索引位置(左闭右开,不包括off+len)
//如果缓冲区(char[]数组)中字符的数量<len个(比如有in(写指针)-out(读指针)个),消费者线程每次从缓冲区(char[]数组)中读取(in-out)个字符放到char[]数组cbuf的[off, off+in-out)索引位置(左闭右开,不包括off+in-out)
public synchronized int read(char cbuf[], int off, int len) throws IOException {
if (!connected) {//检查标记符connected,如果为false,抛出IOException
throw new IOException("Pipe not connected");
} else if (closedByReader) {//检查标记符closedByReader,如果为true,抛出IOException
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
//检查当前这个PipedReader (消费者)对象中引用的生产者线程和生产者线程的状态,如果和标记符closedByWriter还有缓冲区(char[]数组)的写指针(in)不能对应的话,抛出一个IOException
throw new IOException("Write end dead");
}
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {//char[]数组cbuf的[off,off+len)(左闭右开)索引位置是否有越界的检查
throw new IndexOutOfBoundsException();//越界的话,抛出一个IndexOutOfBoundsException
} else if (len == 0) {
return 0;//如果len==0,返回0
}
/* possibly wait on the first character */
int c = read();//先调用read()函数试探性从缓冲区(char[]数组)中读1个字符
if (c < 0) {
return -1;//如果试探性的从缓冲区(char[]数组)中都读不到1个字符,返回-1
}
cbuf[off] = (char)c;//把试探性从缓冲区(char[]数组)中读到的第1个字符放到char[]数组b的off索引位置
int rlen = 1;//累计从缓冲区(char[]数组)中读到的所有字符数量
while ((in >= 0) && (--len > 0)) {
//从缓冲区(char[]数组)中向char[]数组cbuf中读取字符
cbuf[off + rlen] = buffer[out++];
rlen++;//累计从缓冲区(char[]数组)中读到的所有字符数量
if (out >= buffer.length) {
out = 0;//如果读指针(out)>=缓冲区(char[]数组)的长度,设置读指针(out)=0
}
if (in == out) {
/* now empty */
in = -1;//如果消费者线程从缓冲区(char[]数组)中读完字符(char)数据以后读指针(out)=写指针(in),那么,当前消费者线程会设置写指针(in)=-1
}
}
return rlen;//返回累计从缓冲区(char[]数组)中读到的所有字符数量
}
public synchronized boolean ready() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
}
if (in < 0) {
return false;
} else {
return true;
}
}
//关闭这个PipedReader(消费者),其实就是设置标记符closedByReader=true, 设置写指针(in)=-1
public void close() throws IOException {
in = -1;
closedByReader = true;
}
}
三、1个线程向PipedWriter(生产者)写字符数据,1个线程从PipedReader(消费者)读取字符数据的过程
3.1、非循环直接写和非循环直接读
整个过程和字节管道流PipedInputStream(消费者)和PipedOutputStream(生产者)的1个线程向PipedOutputStream(生产者)写字节数据,1个线程从PipedInputStream(消费者)读取字节数据的过程相同,唯一不同的是,字符管道流PipedWriter(生产者)和PipedReader(消费者)写入和读取的缓冲区是字符数组char[] buffer,字节管道流PipedInputStream(消费者)和PipedOutputStream(生产者)写入和读取字节数组缓冲区byte[] buffer详细过程请参考我的另一篇博客:
9、PipedInputStream和PipedOutputStream的源码分析和使用方法详细分析
3.2、加锁循环写和非加锁循环读到byte[]数组b中再处理
同3.1。
四、多个(大于等于2个)线程向PipedWriter(生产者)写字符数据,多个线程从PipedReader(消费者)读取字符数据的过程
4.1、循环直接写和循环直接读
package com.xxx.pipe.PipedReaderAndPipedWriter;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipeTest {
public static void main(String[] args) throws IOException, InterruptedException {
final PipedWriter writer = new PipedWriter();
final PipedReader reader = new PipedReader(writer);
Thread consumer1 = new Thread(new Runnable() {
@Override
public void run() {
try {
char[] readChars = new char[11];
while (reader.read(readChars) != -1) {//read()函数是阻塞的
System.out.println(Thread.currentThread().getName() + " read:" + new String(readChars));
}
} catch (IOException e) {
}
}
}, "consumer1-Thread");
Thread consumer2 = new Thread(new Runnable() {
@Override
public void run() {
try {
char[] readChars = new char[11];
while (reader.read(readChars) != -1) {//read()函数是阻塞的
System.out.println(Thread.currentThread().getName() + " read:" + new String(readChars));
}
} catch (IOException e) {
}
}
}, "consumer2-Thread");
Thread producer1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 1; i <= 3; i++) {
writer.write("Hello Pipe" + i);
System.out.println(Thread.currentThread().getName() + " write:" + "Hello Pipe" + i);
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}, "producer1-Thread");
Thread producer2 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 4; i <= 6; i++) {
writer.write("Hello Pipe" + i);
System.out.println(Thread.currentThread().getName() + " write:" + "Hello Pipe" + i);
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}, "producer2-Thread");
consumer1.start();
consumer2.start();
producer1.start();
producer2.start();
}
}
程序运行结果,如下所示:

main线程构造PipedWriter(生产者)和PipedReader(消费者)的过程如下:

向PipedWriter(生产者)写字符数据的生产者线程的执行过程如下:

从PipedReader(消费者)读取字符数据的消费者线程的执行过程如下:

4.1.1、循环直接写和循环直接读时多个(大于等于2个)生产者线程和多个(大于等于2个)消费者线程处理数据的过程
当使用者执行4.1中的代码时,多个(大于等于2个)个生产者线程和多个(大于等于2个)消费者线程处理数据的过程如下:
①、main线程初始化一个缓冲区(char[]数组),长度为1024(默认值),然后producer1-Thread(简称生产者线程1)和producer2-Thread(简称生产者线程2)都会根据使用者创建的字符串在堆中创建了自己要写入缓冲区(char[]数组)中的字符数组,生产者线程1使用的字符串为"Hello Pipe1",生产者线程2使用的字符串为"Hello Pipe2",如下所示:

②、然后producer1-Thread(简称生产者线程1)和producer2-Thread(简称生产者线程2)会同时调用PipedReader.class::recevice(char c[], int off, int len)函数,由于这个函数是一个线程同步的函数,所以同一时刻,只有获取到锁的线程才能进入后续Monitor监控的代码片段(本示例中是生产者线程1先获取到了锁),另外一个没有获取到锁的线程只能等待获取到锁的生产者线程1释放掉锁,如下所示:

③、获取到锁的生产者生产者线程1将自己堆中引用的字符数组填充到缓冲区(char[]数组),当生产者线程填充完缓冲区之后,写指针变量int in=11,读指针变量int out=0,Thread writeSide = 当前这个生产者线程(Thread)对象,生产者线程会把自己线程栈中修改的变量最终刷新到堆中PipedReader对象中,以确保其它消费者线程的线程栈从堆中读取这3个变量时,这3个变量已经为修改后的值,如下所示:

④、然后consumer1-Thread(简称消费者线程1)和consumer2-Thread(简称消费者线程2)会同时调用PipedReader.class::read(char cbuf[], int off, int len)函数,由于这个函数也是一个线程同步的函数,所以同一时刻,只有获取到锁的线程才能进入后续Monitor监控的代码片段(本示例中是消费者线程1先获取到了锁),另外一个没有获取到锁的线程只能等待获取到锁的消费者线程1释放掉锁,如下所示:

⑤、消费者线程读缓冲区(char[]数组)的过程中会不断地执行out++(读指针)以读取缓冲区(char[]数组)中的可用字节并返回,直到out(读指针)in(写指针)或者使用者创建的字符数组读满了,如果往使用者创建的字符数组中读取字符的过程中out(读指针)in(写指针),会修改in(写指针)=-1(但是这里在多核cpu上执行的时候,生产者线程2此时会继续将自己堆中引用的字符数组填充到缓冲区(char[]数组)中,这个过程会修改in(写指针),使得最后一次读取缓冲区(char[]数组)之前的所有读取,都不会让out读指针和写指针相遇,也就是out(读指针)==in(写指针)),并且每次同步执行PipedReader.class::read(char cbuf[], int off, int len)函数时,都会更新Thread readSide = 当前这个消费者线程(Thread)对象,消费者线程也会把自己线程栈中修改的变量最终刷新到堆中PipedReader对象中,以确保其它消费者线程的线程栈从堆中读取这3个变量时,这3个变量已经为修改后的值,如下所示:

⑥、获取了锁的producer2-Thread(简称生产者线程2),将自己堆中的字符数组填充到缓冲区(char[]数组),获取了锁的consumer1-Thread(简称消费者线程1),也会从缓冲区(char[]数组)的out(读指针)和in(写指针)之间不断地读取字符到自己的字符数组中,如下所示:


⑦、获取了锁的producer1-Thread(简称生产者线程1),将自己堆中的字符数组填充到缓冲区(char[]数组),获取了锁的consumer1-Thread(简称消费者线程1),也会从缓冲区(char[]数组)的out(读指针)和in(写指针)之间不断地读取字符到自己的字符数组中,如下所示:


⑧、获取了锁的producer2-Thread(简称生产者线程2),将自己堆中的字符数组填充到缓冲区(char[]数组),获取了锁的consumer1-Thread(简称消费者线程1),也会从缓冲区(char[]数组)的out(读指针)和in(写指针)之间不断地读取字符到自己的字符数组中,如下所示:


⑨、获取了锁的producer1-Thread(简称生产者线程1),将自己堆中的字符数组填充到缓冲区(char[]数组),获取了锁的consumer1-Thread(简称消费者线程1),也会从缓冲区(char[]数组)的out(读指针)和in(写指针)之间不断地读取字符到自己的字符数组中,如下所示:


⑩、获取了锁的producer2-Thread(简称生产者线程2),将自己堆中的字符数组填充到缓冲区(char[]数组),获取了锁的consumer2-Thread(简称消费者线程2),也会从缓冲区(char[]数组)的out(读指针)和in(写指针)之间不断地读取字符到自己的字符数组中,如下所示:



浙公网安备 33010602011771号