个人笔记 Java多线程(五)

这段笔记是参照b站教程BV1Rv411y7MU整理而来的,用于个人备忘以便复习,需要的朋友可以自取。

线程间通讯

1. 等待/通知机制

在当线程编程中,要执行的操作要满足一定的条件才能执行,可以把这个操作放在if语句中。
在多线程编程中,可能A线程的条件暂时没有被满足,但稍后其他线程B可能会更新条件使得A线程的条件得到满足,所以可以将A线程暂停直到条件满足后再唤醒。

伪代码:

//原子操作
atomics{
    while(条件不满足){
        等待
    }
    当线程条件被满足之后,继续执行之后的代码
}

1.1 等待/通知机制的实现

  • object.wait()
    Object类中wait()方法可以使执行当前代码的线程等待,直到接到通知或被中断为止。
    注意:
  1. wait()方法只能在同步代码块中由锁对象调用
  2. 调用wait()方法会释放锁!

伪代码:

//调用wait()之前要获得对象锁
synchronized(锁对象){
    while(条件不成立){
        //通过锁对象调用wait方法暂停线程
        锁对象.wait();
    }
    
    //线程条件满足后,继续向下执行
    ......
}
  • object.notify()
    Object类的notify()可以唤醒线程,该方法也必须在同步代码块中由锁对象调用。
    没有用锁对象调用wait()/notifyt()方法会抛出IllegalMonitorStateExeption异常。
    如果由多个等待线程,但notify()方法只能唤醒其中一个。但是唤醒是随机的
    在同步代码块中调用wait()方法会立刻释放锁对象。但是调用notify()方法后,它并不会立刻释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般将notify()放在同步代码块的最后

伪代码:

synchronized(锁对象){
    //执行修改保护条件的代码
    ......

    //唤醒其他线程
    锁对象.notify();
}

案例演示:

/**
 * 演示wait/notify方法需要放在同步代码块中
 * 任何对象都可以调用wait/notify
 */
public class Test {
    public static void main(String[]args) throws InterruptedException {

        String text = "xiye";//定义一个字符串作为锁对象

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (text){
                    System.out.println("Thread-0开始等待。。。"+System.currentTimeMillis());

                    try {
                        text.wait();//转入BLOCKED阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("Thread-0等待结束。。。"+System.currentTimeMillis());
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(text){
                    System.out.println("Thread-1开始执行。。。"+System.currentTimeMillis());
                    text.notify();
                    System.out.println("Thread-1开始唤醒。。。"+System.currentTimeMillis());
                }
            }
        });

        t1.start();//开启t1线程,进入wait
        Thread.sleep(3000);//主线程睡眠3s,确保t1进入wait状态
        t2.start();
    }
}

notify执行后不会立刻释放锁对象。
在锁对象执行notify方法之后,当前线程仍然在继续执行,当同步代码块被全部执行完之后,才会释放锁对象,此时wait状态的线程才会被唤醒。

public class Test {
    public static void main(String[]args) throws InterruptedException {

        List<String> list = new ArrayList<>();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list){
                    if(list.size()!=5){
                        System.out.println("线程1开始等待。。。"+System.currentTimeMillis());
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程1结束等待。。。"+System.currentTimeMillis());
                    }
                }
            }
        });

        //向第二个线程添加元素
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list){
                    for (int i=0;i<10;i++){
                        list.add(String.valueOf(i));
                        System.out.println("线程2添加了"+(i+1)+"个数据");

                        if(list.size()==5){
                            list.notify();//等待当前代码块全部执行完之后才会释放锁对象。
                            System.out.println("线程2开始唤醒。。。"+System.currentTimeMillis());
                        }
                        try {

                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
  • interrupt()方法会中断wait()
    当线程处于wait()状态的时候,调用线程对象的interrupt()方法会中断线程的等待状态,会产生InterruptedExeption异常。
public class Test01 {
    public static void main(String[]args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();

        Thread.sleep(2000);

        myThread.interrupt();
    }

    private static final Object Lock = new Object();
    static class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (Lock){

                try {
                    System.out.println("开始等待。。。"+System.currentTimeMillis());
                    Lock.wait();
                    System.out.println("等待结束"+System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("等待被中断了");
                }

            }
        }
    }
}
  • notify()与notifyAll()
    notify()一次只能唤醒一个线程,如果有多个等待线程就只能随机唤醒其中一个
    想要唤醒所有等待线程就要调用notifyAll()。

下述代码中实现了两个锁obj和obj1,定义的四个线程中三个实现obj锁而最后一个实现obj1锁。notifyAll同样必须是现在同步代码块之中,所以notifyAll只能唤醒锁对象相同的线程。即锁对象为obj的同步代码块只能唤醒t1,t2,t3线程,而t4线程则会继续睡眠。
此时在临界区代码中内嵌一层同步代码块,其锁对象为obj1以唤醒t4线程。但这个时候控制台打印时,唤醒部分t4先于其余三个线程打印。原因是notify唤醒线程必须等到当前代码块执行完毕后才能释放锁对象。此时obj未向还未释放,而obj1对象的代码块已经结束,所以t4先于其余线程被唤醒。

public class Test01 {
    public static void main(String[]args) throws InterruptedException {
        Object obj = new Object();
        Object obj1 = new Object();
        SubThread t1 = new SubThread(obj);
        SubThread t2 = new SubThread(obj);
        SubThread t3 = new SubThread(obj);
        SubThread t4 = new SubThread(obj1);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t4.setName("t4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();

        Thread.sleep(2000);

        //唤醒子线程
        synchronized (obj){
            obj.notifyAll();
            synchronized (obj1){
                obj1.notifyAll();
            }
        }

    }

    static class SubThread extends Thread{
        private Object lock;

        public SubThread(Object lock){
            this.lock=lock;
        }

        @Override
        public void run() {
            synchronized (this.lock){

                try {
                    System.out.println(Thread.currentThread().getName()+"开始wait"+System.currentTimeMillis());
                    this.lock.wait();
                    System.out.println(Thread.currentThread().getName()+"结束wait"+System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
  • wait(long)的使用
    wait(long)带有long类型参数的wait()等待,如果参数在指定的时间内没有被唤醒,超时后会自动唤醒。

    参数单位为毫秒 wait(5000)为睡眠5s,5s内没有被通知唤醒则主动唤醒。

  • 通知过早问题
    线程进去wait状态之后,可以通过notify唤醒线程。但如果notify唤醒过早,在等待之前就调用了notify可能会打乱程序正常的逻辑。

    实际上,调用start()方法就是高速线程调度器,当前线程准备就绪,线程调度器在什么时候开启这个线程不确定,即调用start()方法的顺序不一定就是线程实际开启的顺序。

    以下例子中notify先于wait被执行,导致t1线程一直处于睡眠状态。

public class Test01 {
    public static void main(String[]args) throws InterruptedException {

        final Object lock = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        System.out.println("开始wait");
                        lock.wait();
                        System.out.println("结束wait");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("开锁");
                    lock.notify();
                    System.out.println("结束");
                }
            }
        });
        t2.start();
        t1.start();
    
    }
}

修改思路为:
添加静态标志,判断是否是先wait再notify

public class Test01 {
    public static boolean isFirst = true;//定义静态变量作为是否第一个运行的线程标志
    public static void main(String[]args) throws InterruptedException {

        final Object lock = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){

                    while(isFirst){
                        try {
                            System.out.println("开始wait");
                            lock.wait();
                            System.out.println("结束wait");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("开锁");
                    lock.notify();
                    System.out.println("结束");
                    isFirst=false;//通知之后把第一个线程标志改为false
                }
            }
        });

        t2.start();
        t1.start();
    }
}

2. 生产者消费者模式

在Java中,负责产生数据的模块是生产者,负责使用数据的模块是消费者。生产者和消费者是用来解决数据平衡问题的。没有数据的时候消费者需要等待。

生产者类

public class ProductThread extends Thread{
    private ValueOP object;

    public ProductThread(ValueOP obj){
        this.object=obj;
    }

    //生产者生产数据调用ValueOP的setValue给value赋值
    @Override
    public void run() {
        while(true){
            try {
                object.setValue();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

消费者类

public class ConsumerThread extends Thread{

    //消费者使用数据,就是使用getValue取出value值
    private ValueOP object;

    public ConsumerThread(ValueOP obj){
        this.object=obj;
    }

    @Override
    public void run() {
        while(true){
            try {
                object.getValue();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 一生产者一消费者

产品类:

public class ValueOP {

    private String value = "";

    //修改vale
    public void setValue() throws InterruptedException {
        //如果value不是空串就等待
        synchronized (this){
            if(!value.equalsIgnoreCase("")){
                this.wait();
            }
            //设置字段的值
            String value = System.currentTimeMillis()+" - "+System.nanoTime();
            System.out.println("set value: --> "+value);
            this.value = value;
            this.notify();
        }
    }

    //读取value
    public void getValue() throws InterruptedException {
        synchronized (this){
            //如果value是空串就等待
            if(value.equalsIgnoreCase("")){
                this.wait();
            }
            //不是就读取value值
            System.out.println("get value: --> "+this.value);
            this.value="";
            this.notify();
        }
    }
}

主类

public class Test {
    public static void main(String[]args){
        ValueOP op = new ValueOP();
        ProductThread productThread = new ProductThread(op);
        ConsumerThread consumerThread = new ConsumerThread(op);
        
        productThread.start();
        consumerThread.start();
    }
}
  • 多生产者多消费者

多生产者多消费者模式中除了要修改value中set/get中if为while外,还需要将notify改为notifyAll,原因是notify唤醒对象是随机的,notify不能保证是生产者唤醒消费者,所以可能会出现假死(所有线程都进入等待状态)的情况。
修改一生产者一消费者中产品类为:

public class ValueOP {

    private String value = "";

    //修改vale
    public void setValue() throws InterruptedException {
        //如果value不是空串就等待
        synchronized (this){
            while(!value.equalsIgnoreCase("")){
                this.wait();
            }
            //设置字段的值
            String value = System.currentTimeMillis()+" - "+System.nanoTime();
            System.out.println("set value: --> "+value);
            this.value = value;
            this.notifyAll();
        }
    }

    //读取value
    public void getValue() throws InterruptedException {
        synchronized (this){
            //如果value是空串就等待
            while(value.equalsIgnoreCase("")){
                this.wait();
            }
            //不是就读取value值
            System.out.println("get value: --> "+this.value);
            this.value="";
            this.notifyAll();
        }
    }
}

主类

public class Test {
    public static void main(String[]args){
        ValueOP op = new ValueOP();
        ProductThread p1 = new ProductThread(op);
        ConsumerThread c1 = new ConsumerThread(op);
        ProductThread p2 = new ProductThread(op);
        ConsumerThread c2 = new ConsumerThread(op);
        ProductThread p3 = new ProductThread(op);
        ConsumerThread c3 = new ConsumerThread(op);

        p1.start();
        p2.start();
        p3.start();
        c1.start();
        c2.start();
        c3.start();
    }
}

2.1 生产者消费者操作栈

生产者类

public class ProductThread extends Thread{
    private MyStack stack;

    public ProductThread(MyStack stack){
        this.stack=stack;
    }

    @Override
    public void run() {
        while(true){
            try {
                this.stack.push();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者类

public class ConsumerThread extends Thread{
    private MyStack stack;

    public ConsumerThread(MyStack stack){
        this.stack = stack;
    }

    @Override
    public void run() {
        while(true) {
            try {
                this.stack.pop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 一生产一消费操作栈

产品类

public class MyStack {

    private final List<Object> list = new ArrayList<>();//定义集合模拟栈
    private static final int MAX = 1;//集合最大容量

    //定义方法模拟入栈
    public synchronized void push() throws InterruptedException {

        //当栈中数据已满的时候就等待
        if(list.size()>=MAX){
            System.out.println(Thread.currentThread().getName()+" begin wait...");
            this.wait();
        }
        String data = "data -- "+Math.random()*1000;
        System.out.println("添加了数据: "+data);
        list.add(data);

        this.notify();
    }

    //定义一个方法模拟出栈
    public synchronized void pop() throws InterruptedException {
        if(list.size()==0){
            System.out.println("没有数据可以出栈,开始等待");
            this.wait();//没有数据就等待
        }

        System.out.println(Thread.currentThread().getName()+"添加了数据 --> "+list.remove(0));
        this.notify();
    }
}

主类

public class Test {
    public static void main(String[]args){
        MyStack stack = new MyStack();

        ConsumerThread consumerThread = new ConsumerThread(stack);
        ProductThread productThread = new ProductThread(stack);

        productThread.start();
        consumerThread.start();
    }
}
  • 一生产者多消费者操作栈

产品类

public class MyStack {

    private final List<Object> list = new ArrayList<>();//定义集合模拟栈
    private static final int MAX = 1;//集合最大容量

    //定义方法模拟入栈
    public synchronized void push() throws InterruptedException {

        //当栈中数据已满的时候就等待
        while(list.size()>=MAX){
            System.out.println(Thread.currentThread().getName()+" begin wait...");
            this.wait();
        }
        String data = "data -- "+Math.random()*1000;
        System.out.println("添加了数据: "+data);
        list.add(data);

        this.notifyAll();
    }

    //定义一个方法模拟出栈
    public synchronized void pop() throws InterruptedException {
        while(list.size()==0){
            System.out.println("没有数据可以出栈,开始等待");
            this.wait();//没有数据就等待
        }

        System.out.println(Thread.currentThread().getName()+"添加了数据 --> "+list.remove(0));
        this.notifyAll();
    }
}

主类

public class Test {
    public static void main(String[]args){
        MyStack stack = new MyStack();

        ConsumerThread c1 = new ConsumerThread(stack);
        ConsumerThread c2 = new ConsumerThread(stack);
        ConsumerThread c3 = new ConsumerThread(stack);
        ProductThread productThread = new ProductThread(stack);

        productThread.start();
        c1.start();
        c2.start();
        c3.start();
    }
}
  • 多生产者多消费者操作栈

主类
直接修改主类即可

public class Test {
    public static void main(String[]args){
        MyStack stack = new MyStack();

        ConsumerThread c1 = new ConsumerThread(stack);
        ConsumerThread c2 = new ConsumerThread(stack);
        ConsumerThread c3 = new ConsumerThread(stack);
        ProductThread p1 = new ProductThread(stack);
        ProductThread p2 = new ProductThread(stack);
        ProductThread p3 = new ProductThread(stack);
        
        p1.setName("生产者1");
        p2.setName("生产者2");
        p3.setName("生产者3");
        c1.setName("消费者1");
        c2.setName("消费者2");
        c3.setName("消费者3");
        p1.start();
        p2.start();
        p3.start();
        c1.start();
        c2.start();
        c3.start();
    }
}

3. 通过管道流实现线程间通讯

在java.io包中的PipeStream管道流用于在线程之间传送数据,一个线程发送数据到输出管道,另一个线程从输入输入管道中读取数据。

包括的类为:

字节流字符流
PipedInputStreamPipedReader
PipedOutputStreamPipedWriter
/**
 * 使用管道字节流在线程之间传递数据
 */
public class PipeStream {
    public static void main(String[]args) throws IOException {
        //定义管道字节流
        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream();
        //在输入输出之间建立连接
        inputStream.connect(outputStream);

        //向管道中写入数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    writePipe(outputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //读取数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    readPipe(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    //写入管道流
    public static void writePipe(PipedOutputStream out) throws IOException {
        for (int i = 0; i < 100; i++) {
            String data = "data - "+i;
            out.write(data.getBytes(StandardCharsets.UTF_8));//把字节数组写入到输出管道
        }
        out.close();//关闭管道流
    }


    //从管道流读出数据
    public static void readPipe(PipedInputStream in) throws IOException {
        byte[] bytes=new byte[1024];
        //从字节流读取字节操作到数组中
        int len = in.read(bytes);//返回读到的任何字节数,没有就返回-1
        while (len != -1){
            //把byte数组从0开始到len个字节转换成String输出
            System.out.println(new String(bytes,0,len));
            len = in.read(bytes);//继续从管道中读取数据
        }
        in.close();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值