Sychronized线程通信

管道流的种类

管道流是用来在多个线程之间进行信息传递的Java流。管道流分为字节流管道流和字符管道流
字节管道流:PipedOutputStream和PipedInputStream。
字符管道流:PipedWriter和PipedReader。

字节管道流&字符管道流

字节管道流是通过底层一个是byte数组存储数据的。Java的管道输入与输出实际上使用的是一个循环缓冲数组来实现的。输入流PipedInputStream从这个循环缓冲数组中读数据,输出流PipedOutputStream往这个循环缓冲数组中写入数据。当这个循环缓冲数组已满的时候,输入流PipedOutputStream所在的线程将阻塞,当这个循环缓冲数据为空的时候,输出流PipedInputStream所在的线程将阻塞。
字符管道流是通过底层一个是char数组存储数据的。

注意事项

  • 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能造成死锁;
  • 管道流的输入和输出的成对的,一个输入流只能对应输出流,使用构造函数或者connect函数进行连接;
  • 一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造函数;
  • 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
  • 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
  • 管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值。

管道流的使用

自符管道流

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;


public class PipedDemo {
    private static class Writer implements Runnable{
        private PipedWriter pipedWriter;


        private Writer(PipedWriter writer){
            pipedWriter = writer;
        }
        @Override
        public void run() {
            int receive;
            try {
                while ((receive = System.in.read()) != -1){
                    System.out.println(Thread.currentThread().getName() + "写入字符:" + (char)receive);
                    pipedWriter.write(receive);
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    pipedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }
    }


    private static class Printer implements Runnable{
        private PipedReader pipedReader;


        private Printer(PipedReader reader){
            this.pipedReader = reader;
        }
        @Override
        public void run() {
            int receive;
            try {
                while ((receive = pipedReader.read()) != -1){
                    System.out.println(Thread.currentThread().getName() + "打印字符:" + (char)receive);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();


        out.connect(in);


        Thread printThread = new Thread(new Printer(in),"打印线程");
        printThread.start();


        Thread writeThread = new Thread(new Writer(out),"写入线程");
        writeThread.start();
    }
}

字节流管道

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Scanner;


public class PipedDemo01 {
    private static class Writer implements Runnable{
        private PipedOutputStream pipedOutputStream;
        public PipedOutputStream getPipedOutputStream() {
            return pipedOutputStream;
        }
        private Writer(PipedOutputStream outputStream){
            this.pipedOutputStream = outputStream;
        }
        @Override
        public void run() {
            Scanner scanner = new Scanner(System.in);
            String receive = scanner.nextLine();
            System.out.println(Thread.currentThread().getName() + "写入字符串:" + receive);
            try {
                pipedOutputStream.write(receive.getBytes());
                pipedOutputStream.flush();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    pipedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private static class Printer implements Runnable{
        private PipedInputStream pipedInputStream;


        private Printer(PipedInputStream inputStream){
            this.pipedInputStream = inputStream;
        }


        public PipedInputStream getPipedInputStream() {
            return pipedInputStream;
        }
        @Override
        public void run() {
            try {
                byte[] buf = new byte[1024];
                int len = pipedInputStream.read(buf);
                String receive = new String(buf,0,len);
                System.out.println(Thread.currentThread().getName() + "打印字符串:" + receive);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    pipedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String[] args) throws IOException {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream();
        out.connect(in);


        Thread writeThread = new Thread(new Writer(out),"写入线程");
        writeThread.start();
        Thread printThread = new Thread(new Printer(in),"打印线程");
        printThread.start();
    }
}

管道流源码分析

PipedReader构造器

    public PipedReader(PipedWriter src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }

    public PipedReader(PipedWriter src, int pipeSize) throws IOException {
        initPipe(pipeSize);
        connect(src);
    }

    public PipedReader() {
        initPipe(DEFAULT_PIPE_SIZE);
    }

    public PipedReader(int pipeSize) {
        initPipe(pipeSize);
    }

可以看到,无论哪种构造方法都调用了initPiped方法。

private void initPipe(int pipeSize) {
        if (pipeSize <= 0) {
            throw new IllegalArgumentException("Pipe size <= 0");
        }
        buffer = new char[pipeSize];
}

initPiped方法首先校验传入的参数,然后创建了传入的参数大小的char数组。

PipedWriter构造器

public PipedWriter() {
}

PipedWrier.connect()方法

public synchronized void connect(PipedReader snk) throws IOException {
        if (snk == null) {
            throw new NullPointerException();
        } else if (sink != null || snk.connected) {
            throw new IOException("Already connected");
        } else if (snk.closedByReader || closed) {
            throw new IOException("Pipe closed");
        }


        sink = snk;
        snk.in = -1;//写入位置
        snk.out = 0;//输出位置
        snk.connected = true;
    }

PipedWriter类中有个PipedReader的引用。

private PipedReader sink;

connect方法是一个同步的方法:

  1. 首先校验PipedReader的合法性,非空,未连接且未关闭;
  2. 初始化缓存数组的写入位置和输出位置。此时没有任何数据发生写入和输出,所有都是默认值。

PipedWriter.wtite()方法

public void write(int c)  throws IOException {
	if (sink == null) {
		throw new IOException("Pipe not connected");
	}
	sink.receive(c);
}

写入字符到管道字符输出流中。PipedWiter.wtite()方法。其调用了PipedReader.receive()方法写入数据。

PipedReader.receive()方法

synchronized void receive(int c) throws IOException {
        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) {
            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) {
            in = 0;
            out = 0;
        }
        buffer[in++] = (char) c;
        if (in >= buffer.length) {
            in = 0;
        }
    }

首先校验管道是否正常,然后判断in == out,是则说明缓冲区已满,不能再次写入了,所以在while循环中唤醒所有读的线程(读的线程也许出于wait状态)。然后当前写入的线程休眠1s。
当否时,说明有空间可以写入数据。如果in小于0,说明是首次进入,初始化,in= 0,out = 0,。如果写入的数据超过缓存数组的长度,那就重置为0,从头开始写入。此时就把缓存数组当做是一个环形。

PipedReader.read()方法

public synchronized int read()  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");
        }


        readSide = Thread.currentThread();
        int trials = 2;
        while (in < 0) {
            if (closedByWriter) {
                /* closed by writer, return EOF */
                return -1;
            }
            if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
                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++];
        if (out >= buffer.length) {
            out = 0;
        }
        if (in == out) {
            /* now empty */
            in = -1;
        }
        return ret;
    }

首先是管道状态检验。while循环监听判断是否有写线程写数据,如果没有则等待(每秒检查一次),并唤醒写线程(写线程可能wait)。读取buffer中的数据。out>=0读到buffer的最后一个元素,则把out置为0,下次从下标0开始继续读。如果在read方法读取数据时,发生in == out,则把in置为-1。置为初始状态。相当于清空了缓存区,从缓冲区下标0开始读写。
管道流的原理其实很简单,就是通过wait()和notifyAll()来控制buffer是否可读写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值