Java多線程之等待喚醒機(jī)制

1 等待喚醒機(jī)制

1.1 線程間通信

概念:多個(gè)線程在處理同一個(gè)資源,但是處理的動(dòng)作(線程的任務(wù))卻不相同。

  • 為何要處理線程之間的通訊?

? 讓多線程在訪問(wèn)同一份資源時(shí)按照一定的規(guī)律進(jìn)行。

  • 如何保證線程間通信有效利用資源:

? 多個(gè)線程在處理同一個(gè)資源,并且任務(wù)不同時(shí),需要線程通信來(lái)幫助解決線程之間對(duì)同一個(gè)變量的使用或操作,避免對(duì)同一共享變量的爭(zhēng)奪————等待喚醒機(jī)制

1.2 等待喚醒機(jī)制

等待喚醒機(jī)制

  • 是多個(gè)線程間的一種協(xié)作機(jī)制
  • 在一個(gè)線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait()), 等待其他線程執(zhí)行完他們的指定代碼過(guò)后 再將其喚醒(notify());在有多個(gè)線程進(jìn)行等待時(shí), 如果需要,可以使用 notifyAll()來(lái)喚醒所有的等待線程。
  • wait/notify 就是線程間的一種協(xié)作機(jī)制。

等待喚醒中的方法

  • wait:線程不再活動(dòng),不再參與調(diào)度,進(jìn)入 wait set 中,因此不會(huì)浪費(fèi) CPU 資源,也不會(huì)去競(jìng)爭(zhēng)鎖了,這時(shí)的線程狀態(tài)即是 WAITING。它還要等著別的線程執(zhí)行一個(gè)特別的動(dòng)作,也即是“通知(notify)”在這個(gè)對(duì)象上等待的線程從wait set 中釋放出來(lái),重新進(jìn)入到調(diào)度隊(duì)列(ready queue)中
  • wait(long timeout):等待指定的毫秒數(shù)
  • notify:則選取所通知對(duì)象的 wait set 中的一個(gè)線程釋放;例如,餐館有空位后,等候就餐最久的顧客最先入座。
  • notifyAll:則釋放所通知對(duì)象的 wait set 上的全部線程,優(yōu)先級(jí)別高的線程優(yōu)先調(diào)度。

注意:

哪怕只通知了一個(gè)等待的線程,被通知線程也不能立即恢復(fù)執(zhí)行,因?yàn)樗?dāng)初中斷的地方是在同步塊內(nèi),而此刻它已經(jīng)不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競(jìng)爭(zhēng)),成功后才能在當(dāng)初調(diào)用 wait 方法之后的地方恢復(fù)執(zhí)行。

總結(jié)如下:

  • 如果能獲取鎖,線程就從 WAITING 狀態(tài)變成 RUNNABLE 狀態(tài);
  • 否則,從 wait set 出來(lái),又進(jìn)入 entry set,線程就從 WAITING 狀態(tài)又變成 BLOCKED 狀態(tài)

調(diào)用wait和notify方法需要注意的細(xì)節(jié)

  • wait方法與notify方法必須要由同一個(gè)鎖對(duì)象調(diào)用。因?yàn)椋簩?duì)應(yīng)的鎖對(duì)象可以通過(guò)notify喚醒使用同一個(gè)鎖對(duì)象調(diào)用的wait方法后的線程。
  • wait方法與notify方法是屬于Object類(lèi)的方法的。因?yàn)椋烘i對(duì)象可以是任意對(duì)象,而任意對(duì)象的所屬類(lèi)都是繼承了Object類(lèi)的。
  • wait方法與notify方法必須要在同步代碼塊或者是同步函數(shù)中使用,否則會(huì)拋出異常IIlegalMonitorStateException。因?yàn)椋罕仨氁ㄟ^(guò)鎖對(duì)象調(diào)用這2個(gè)方法。

1.3 生產(chǎn)者與消費(fèi)者問(wèn)題

等待喚醒機(jī)制其實(shí)就是經(jīng)典的“生產(chǎn)者與消費(fèi)者”的問(wèn)題。

生產(chǎn)者和消費(fèi)者共享同一個(gè)資源,并且生產(chǎn)者和消費(fèi)者之間相互依賴(lài),互為條件

  • 對(duì)于生產(chǎn)者,沒(méi)有生產(chǎn)產(chǎn)品之前,要通知消費(fèi)者等待。而生產(chǎn)產(chǎn)品之后,又要馬上通知消費(fèi)者消費(fèi)
  • 對(duì)于消費(fèi)者,在消費(fèi)之后,要通知生產(chǎn)者已經(jīng)結(jié)束消費(fèi),需要生產(chǎn)新的產(chǎn)品以供消費(fèi)

解決方法:線程同步+線程通訊

1.3.1 信號(hào)燈法(通過(guò)標(biāo)志位)

  • 包子鋪線程生產(chǎn)包子,吃貨線程消費(fèi)包子。當(dāng)包子沒(méi)有時(shí)(包子狀態(tài)為false),吃貨線程等待,包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true),并通知吃貨線程(解除吃貨的等待狀態(tài)),因?yàn)橐呀?jīng)有包子了,那么包子鋪線程進(jìn)入等待狀態(tài)。接下來(lái),吃貨線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。如果吃貨獲取到鎖,那么就執(zhí)行吃包子動(dòng)作,包子吃完(包子狀態(tài)為false),并通知包子鋪線程(解除包子鋪的等待狀態(tài)),吃貨線程進(jìn)入等待。包子鋪線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。
//資源
public class Baozi {
    private String pier;
    private String xianer;
    private boolean flag = false;//包子資源,是否存在
    public Baozi() {
    }
    public Baozi(String pier, String xianer) {
        this.pier = pier;
        this.xianer = xianer;
    }
    public String getPier() {
        return pier;
    }
    public void setPier(String pier) {
        this.pier = pier;
    }
    public String getXianer() {
        return xianer;
    }
    public void setXianer(String xianer) {
        this.xianer = xianer;
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
//消費(fèi)者
public class ChiHuo extends Thread {
    private Baozi bz;
    public ChiHuo(String name, Baozi bz) {
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                if (bz.isFlag() == false) {
                    try {
                        bz.wait();//吃貨等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃貨正在吃" + bz.getPier() + bz.getXianer() + "包子!");
                System.out.println("包子吃完了!");
                bz.setFlag(false);
                bz.notify();//喚醒包子鋪
            }
        }
    }
}
//生產(chǎn)者
public class BaoZiPu extends Thread {
    private Baozi bz;
    public BaoZiPu(String name, Baozi bz) {
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (bz) {
                if (bz.isFlag()) {
                    try {
                        bz.wait();//包子鋪停止生產(chǎn)
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("包子鋪開(kāi)始做包子");
                if (count % 2 == 0) {
                    bz.setPier("冰皮");
                    bz.setXianer("五仁");
                } else {
                    bz.setPier("薄皮");
                    bz.setXianer("牛肉大蔥");
                }
                count++;
                bz.setFlag(true);
                System.out.println("包子造好了:" + bz.getPier() + bz.getXianer());
                System.out.println("吃貨來(lái)吃包子吧");
                bz.notify();//喚醒吃貨吃包子
            }
        }
    }
}
public class BaoZiTest {
    public static void main(String[] args) {
        Baozi bz = new Baozi();
        BaoZiPu bzp = new BaoZiPu("包子鋪", bz);
        ChiHuo ch = new ChiHuo("吃貨", bz);
        bzp.start();
        ch.start();
    }
}
/*
包子鋪開(kāi)始做包子
包子造好了:冰皮五仁
吃貨來(lái)吃包子吧
吃貨正在吃冰皮五仁包子!
包子吃完了!
包子鋪開(kāi)始做包子
包子造好了:薄皮牛肉大蔥
吃貨來(lái)吃包子吧
吃貨正在吃薄皮牛肉大蔥包子!
包子吃完了!
包子鋪開(kāi)始做包子
*/

1.3.2 管程法

  • 生產(chǎn)者:負(fù)責(zé)生產(chǎn)數(shù)據(jù)的模塊(可能是方法、對(duì)象、線程、進(jìn)程)

  • 消費(fèi)者:負(fù)責(zé)處理數(shù)據(jù)的模塊(可能是方法、對(duì)象、線程、進(jìn)程)

  • 緩沖區(qū):消費(fèi)者不能直接使用生產(chǎn)者的數(shù)據(jù),他們之間有個(gè)“緩沖區(qū)”,生產(chǎn)者將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū),消費(fèi)者從緩沖區(qū)拿出數(shù)據(jù)

1607675140541.png
//生產(chǎn)者、消費(fèi)者、產(chǎn)品、容器
public class PCThread {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Provider(container).start();
        new Consumer(container).start();
    }
}

//生產(chǎn)者
class Provider extends Thread {
    SynContainer container;

    public Provider(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("生產(chǎn)者生產(chǎn)第" + i + "只雞");
            container.push(new Chicken(i));
        }
    }
}

//消費(fèi)者
class Consumer extends Thread {
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費(fèi)者消費(fèi)第" + container.pop().id + "只雞");
        }

    }
}

//資源 雞
class Chicken {
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

//緩沖區(qū) 容器
class SynContainer {
    //容器大小
    Chicken[] chickens = new Chicken[10];
    //容器計(jì)數(shù)器
    int count;

    //生產(chǎn)者放入產(chǎn)品
    public synchronized void push(Chicken chicken) {
        //容器滿(mǎn)了,生產(chǎn)者停止生產(chǎn),等待消費(fèi)者消費(fèi)
        if (count == chickens.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //沒(méi)滿(mǎn),則放入產(chǎn)品
        chickens[count] = chicken;
        count++;
        //通知消費(fèi)者消費(fèi)
        this.notifyAll();
    }

    //消費(fèi)者消費(fèi)產(chǎn)品
    public synchronized Chicken pop() {
        //判斷能否消費(fèi)
        if (count == 0) {
            //消費(fèi)者等待生產(chǎn)者生產(chǎn)
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //可以消費(fèi)
        count--;
        Chicken chicken = chickens[count];

        //吃完了通知生產(chǎn)者生產(chǎn)
        this.notifyAll();
        return chicken;
    }
 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容