多线程之线程间通信 ,生产者和消费者,等待和唤醒机制

多线程之线程间通信

学习线程通信之前我们需要简单的了解下生产者和消费者模式。
然后我们通过生产者和消费者 学习到线程间通信 等待与唤醒机制。

1 生产者和消费者

  • 先来看生产者和消费者

生产者 就是生产东西 如生产商品
消费者 就是消费东西 如 卖商品

  • 用代码来描述就是:
        System.out.println("生产1");
        System.out.println("消费1");

  • 用两个线程来表示
package com.company.threadcommunication;

/**
 * @description: 线程通信之 生产者 消费者
 * @author: tizzy
 * @create: 2019-12-18 20:59
 **/
public class ThreadCommunication1 {

    public static void main(String[] args) {
        Communication1 communication = new Communication1();
        new Thread(){
            @Override
            public void run() {
                communication.produce();
            }
        }.start();


        new Thread(){
            @Override
            public void run() {
                communication.consumer();
            }
        }.start();


    }


}

class Communication1{
    int i = 0;
    /**
     * 生产者
     */
    public void produce(){
        while (true){
            i++;
            System.out.println(" 生产者 ---> " + i);
        }

    }


    /**
     * 消费者
     */
    public void consumer(){

        while (true){

            System.out.println(" 消费者 ---> " + i);
        }

    }
}

启动运行生产者线程和消费者线程后发现 生产者和消费者并不是按照我们的意愿在生产和消费。
可以看到生产者一直在生产,消费者也一直在消费,但是消费者存在重复消费的情况。
在这里插入图片描述
我们期望看到生产者生产一个,然后消费者消费一个这样子的运行结果。
但是为什么会出现下面这种情况,原因在于,生产者一直只顾着自己生产,消费者只顾着自己消费,不管有没有被消费过。

如何让两个线程生产一个然后在马上消费一个?我们可以让生产者线程和消费者线程互相通信,当生产者生产一个之后,等着别生产,然后告诉消费者你该消费了,消费者消费完了,先等着别消费了,告诉生产者你赶紧生产 … 一直这样子互相告诉对方信息,放到线程里我们就叫做 线程通信

2 单线程下的线程通信

我们来修改上面的代码
这里需要用到两个方法

线程等待

wait() 让当前线程等着,释放cpu执行权,在当前对象锁中的线程池中排队,将线程临时存储到了线程池中。
当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException

线程唤醒

notify() 唤醒等待的一个线程,让其他线程去执行调度

notifyAll(): 会唤醒线程池中所有的等待的线程。

这些方法必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒改锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。

为什么这些方法定义在Object类中,而不是Thread类中呢?

因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法。

package com.company.threadcommunication;

/**
 * @description: 线程通信
 * @author: tizzy
 * @create: 2019-12-18 20:59
 **/
public class ThreadCommunication2 {

    public static void main(String[] args) {
        Communication2 communication = new Communication2();
        new Thread() {
            @Override
            public void run() {
                while (true){
                    communication.produce();
                }
            }
        }.start();


        new Thread() {
            @Override
            public void run() {
                while (true) {
                    communication.consumer();
                }
            }
        }.start();


    }


}

class Communication2 {

    //对象
    private final Object object = new Object();
    //是否生产
    volatile boolean falg = false;  //没有生产

    int i = 0;

    /**
     * 生产者
     */
    public void produce() {
        synchronized (object) {
            //true 生产
            if (!falg) {
                    i++;
                    System.out.println(" 生产 : " + i);
                //唤醒 消费者去消费
                object.notify();
                falg= true;
            } else {
                //生产了就等着,不在生产,等待消费者去消费
                try {
                    object.wait(); //wait 当前线程处于等待状态  并且释放执行权,释放锁

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    /**
     * 消费者
     */
    public void consumer() {

        synchronized (object){
            //消费
            if(falg){
                System.out.println(" 消费 :  " + i);
                //通知去生产
                object.notify();
                falg= false;
            }else {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果

在这里插入图片描述
可以看到,生产者每次生产一个,然后消费者消费一个。

需要注意的是

  • 1 wait()、notify()、notifyAll()这些方法必须使用在同步中,因为它们是用来操作同步锁上的线程的状态的;
  • 2 同时在使用这些方法时,必须标识它们所属于的锁,标识方式就是:锁对象.wait()、锁对象.notify()、锁对 象.notifyAll()。相同锁的notify(),可以获取相同锁的wait();
  • 3 锁可以是任意对象,所以任意对象调用的方法一定定义在Object类中。

3 多线程下生产者消费者

多线程下就不能使用notify()来唤醒线程了,必须使用notifyAll()来唤醒所有等待的线程。

package com.company.threadcommunication;

import java.util.stream.Stream;

/**
 * @description: 线程通信
 * @author: Administrator
 * @create: 2019-12-18 20:59
 **/
public class ManyThreadManyCommunication3 {

    public static void main(String[] args) {

        Communication3 communication = new Communication3();

        //多个生产者
        Stream.of("p1", "p2", "p3").forEach(s -> {
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        communication.produce(s);
                    }
                }
            }.start();
        });

        //多个消费者
        Stream.of("c1", "c2", "c3").forEach(s -> {
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        communication.consumer(s);
                    }
                }
            }.start();
        });


    }


}

class Communication3 {

    //对象
    private final Object object = new Object();
    //是否生产
    volatile boolean falg = false;  //没有生产

    int i = 0;

    /**
     * 生产者
     */
    public void produce(String name) {
        synchronized (object) {
            //true 生产
            while (!falg) {
                i++;
                System.out.println(name + " 生产 : " + i);
                //唤醒 消费者去消费
                object.notifyAll();
                falg = true;
            }


            //生产了就等着,不在生产,等待消费者去消费
            try {
                object.wait(); //wait 等待线程 释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }


    /**
     * 消费者
     */
    public void consumer(String name) {

        synchronized (object) {
            //消费
            while (falg) {
                System.out.println( name +  " 消费 :  " + i);
                //通知去生产
                object.notifyAll();
                falg = false;
            }


            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }
}

可以看到结果
在这里插入图片描述

注意

生产者和消费者 这里使用while去判断是否需要生产和消费标识

 //消费 
            while (falg) {
                ..... 
            }

 //生产
            while (!falg) {
                ..... 
            }

需要注意的是:
当多个线程这行到这里时候如果没有使用while而用if判断 falg 执行标识,会存在重复生产或消费的情况。

这里假设消费者先抢到cpu执行权,falg 是false 消费者处于等待状态,然后生产者第一个线程进来后发现已经非falg 是true 是需要生产,然后进行生产,此时falg设为false ,第二个生产者线程再进来,if中条件已经为false 不会去唤醒消费者去消费,直接进行生产数据,这样子重复生产,同理情况下消费者亦是如此。

所以这里需要用到while去循环判断状态标识,避免重复消费或生产。

发布了241 篇原创文章 · 获赞 66 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/weixin_38361347/article/details/103606285