線程基本通訊機制--wait和notify

1、線程基本通訊機制

1 wait和notify的用法

wait和notify是Java最基本的線程間通訊機制,體現了線程的交互,用於信息的傳遞。例如在生產者消費者模式中,利用阻塞和喚醒使不一樣線程之間配合實現業務邏輯。java

阻塞階段--wait,調用對象的wait方法,線程進入WAITING狀態,阻塞掛起,釋放鎖
wait阻塞後,直到下面狀況之一發生時,線程纔會被喚醒。ide

  • 其餘線程調用該對象的notify/notifyAll方法。
  • 帶有超時參數的wait方法,發生超時。若是參數是0,則永久等待。
  • 調用線程中斷interrupt()。線程在waiting狀態,會自動響應中斷,拋出中斷異常。

喚醒階段 --notify/notifyAll測試

  • notify:隨機喚醒一個線程
  • notifyAll:喚醒全部線程

2 wait和notify性質

  1. 使用前須要擁有鎖(monitor鎖)
  2. 屬於Object類,底層是final native方法,屬於JVM層代碼。
  3. wait和notify是最基本的線程通訊機制
  4. 同時擁有多把鎖,須要注意鎖的釋放順序
synchronized(this) { 
    while(條件){
       wait();     
    }
}
  • 若是wait方法沒有修飾,表示當前對象this
  • 即便沒有調用喚醒方法,線程仍有可能從掛起狀態變爲可運行狀態(虛假喚醒)。爲防患於未然,常見是不斷測試該線程被喚醒的條件是否被知足,不知足則繼續等待。
synchronized (resourceA) {
    synchronized (resourceB) {
        try {
            resourceA.wait(); // 只釋放resourceA鎖
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意點:調用某個對象的notify,只能喚醒與該對象對應的線程。調用wait也只釋放當前的那把鎖。this

3 wait原理

EntrySet:入口集;WaitSet:等待集;The owner:線程擁有鎖。’
鎖的運行原理:開始線程在入口集和等待集競爭鎖【1】,此時線程A獲取到了鎖【2】,入口集和等待集中的線程進入BLOCKED。此時A能夠正常運行完釋放鎖【6】,也能夠調用wait釋放鎖進入等待集【3】。等待集線程被喚醒【4】後,進入另外一個等待集,與入口集的線程一塊兒競爭鎖【5】。線程

2、常見問題

1 wait和notify實現生產者消費者模式

生產者消費者模式能夠解耦生產者和消費者,使二者更好地配合。
設計

// 生產和消費100個產品
public class ProducerConsumerModelByWaitAndNotify {
    public static void main(String[] args) {
        // 建立倉庫
        Storage storage = new Storage();
        // 建立生產者消費者線程
        Thread producer = new Thread(new ProducerTask(storage));
        Thread consumer = new Thread(new ConsumerTask(storage));
        producer.start();
        consumer.start();
    }
}

class ProducerTask implements Runnable {
    private Storage storage;

    public ProducerTask(Storage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        // 生產100個產品
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

class ConsumerTask implements Runnable {
    private Storage storage;

    public ConsumerTask(Storage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

class Storage {
    private int maxSize;
    private Queue<Date> storage;

    public Storage() {
        this.maxSize = 10; // 隊列最大是10
        this.storage = new LinkedList<>();
    }

    /**
     * wait和notify須要首先獲取到鎖,所以須要使用synchronized方法或者同步代碼塊
     */
    public synchronized void put() {
        // 倉庫已滿,沒法生產更多產品,讓出鎖
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("生產者生產產品,此時倉庫產品數:" + storage.size());
        // 通知消費者消費
        notify();
    }

    public synchronized void take() {
        // 倉庫爲空,沒法獲取到產品,線程讓出鎖
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.poll();
        System.out.println("消費者消費產品,此時倉庫產品數:" + storage.size());
        // 通知生產者生產
        notify();
    }
}

2. 爲何wait須要放在同步代碼塊中使用,而sleep不須要?

主要是爲了讓通訊更加可靠,防止死鎖、永久等待的發生。code

wait放到synchronized代碼中對線程有必定的保護做用。假設沒有synchronized的保護,線程A在運行到wait語句以前,切換到線程B執行了notify語句,此時執行了wait語句釋放鎖後,沒有線程喚醒,致使了永久等待。對象

sleep方法是針對單個線程的,與其餘線程無關,無需放入到同步代碼塊中。blog

3 爲何wait和notify方法定義在Object中,而不是Thread中?

wait和notify是鎖級別操做,而鎖是屬於某個對象的,鎖標識在對象的對象頭中。若是將wait和notify定義在線程中,則會有很大的侷限性。例如每一個線程均可能會休眠。若是某個線程持有多個鎖,並且鎖之間是相互配合的時,wait方法在Thread類中,就沒有辦法實現線程的配合。隊列

調用線程對象.wait會發生什麼?

我的理解: 調用線程對象的wait方法,也就是說以線程爲鎖。wait和notify的初衷就是用來線程間通訊,若是以線程爲鎖,不利於設計流程。

相關文章
相關標籤/搜索