多線程之線程間通訊 ,生產者和消費者,等待和喚醒機制

多線程之線程間通訊

學習線程通訊以前咱們須要簡單的瞭解下生產者和消費者模式。
而後咱們經過生產者和消費者 學習到線程間通訊 等待與喚醒機制。java

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);
        }

    }
}

啓動運行生產者線程和消費者線程後發現 生產者和消費者並非按照咱們的意願在生產和消費。
能夠看到生產者一直在生產,消費者也一直在消費,可是消費者存在重複消費的狀況。
在這裏插入圖片描述
咱們指望看到生產者生產一個,而後消費者消費一個這樣子的運行結果。
可是爲何會出現下面這種狀況,緣由在於,生產者一直只顧着本身生產,消費者只顧着本身消費,無論有沒有被消費過。多線程

如何讓兩個線程生產一個而後在立刻消費一個?咱們可讓生產者線程和消費者線程互相通訊,當生產者生產一個以後,等着別生產,而後告訴消費者你該消費了,消費者消費完了,先等着別消費了,告訴生產者你趕忙生產 ........ 一直這樣子互相告訴對方信息,放到線程裏咱們就叫作 線程通訊ide

2 單線程下的線程通訊

咱們來修改上面的代碼
這裏須要用到兩個方法學習

線程等待

wait() 讓當前線程等着,釋放cpu執行權,在當前對象鎖中的線程池中排隊,將線程臨時存儲到了線程池中。
當前線程必須擁有此對象的監視器(鎖),不然拋出java.lang.IllegalMonitorStateExceptionthis

線程喚醒

notify() 喚醒等待的一個線程,讓其餘線程去執行調度spa

notifyAll(): 會喚醒線程池中全部的等待的線程。線程

這些方法必須使用在同步中,由於必需要標識wait、notify等方法所屬的鎖。同一個鎖上的notify,只能喚醒改鎖上wait的線程。默認是this.wait();this.notify();this.notifyAll()。3d

爲何這些方法定義在Object類中,而不是Thread類中呢?code

由於這些方法必須標識所屬的鎖,而鎖能夠是任意對象,任意對象能夠調用的方法必然是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去循環判斷狀態標識,避免重複消費或生產。

更多請關注微信公衆號

公衆號二維碼.png

相關文章
相關標籤/搜索