多線程知識梳理(4) synchronized 三部曲之等待 通知模型

1、概述

在前面兩篇文章當中,咱們介紹了synchronized的基本使用和原理,可是在使用synchronized保證數據一致性的同時,咱們但願可以讓線程之間進行一些交互邏輯,也是咱們今天要介紹的等待/通知模型,那麼就須要使用到wait/notifybash

2、等待/通知相關方法

2.1 方法說明

下面,咱們先介紹等待/通知機制的相關方法,首先要說明兩點:ui

  • 這些都是Object定義的方法
  • 調用這些方法的前提條件是:該線程已經得到了Object對象所關聯的鎖,也就是說它們須要位於synchronized修飾的同步代碼塊中。

**(a) wait() ** 調用該方法的線程進入等待狀態,並釋放它所獲取的對象鎖,只有出現這兩種狀況之一,它纔會從wait方法中返回,不然將會一直處於等待狀態:spa

  • 其它線程經過notify / notifyAll方法通知該線程,而且該線程獲取到了對象鎖
  • 線程被中斷

(b) wait(long) / wait(long, int)wait方法相同,差異是增長一種從wait方法返回的狀況:等待的時間已經到了,而且獲取到了對象鎖。線程

(c) notify() 通知位於等待隊列中的第一個線程,使其從wait()方法返回,而被通知的線程的繼續執行須要等到它得到對象所爲止。 須要注意,調用notify方法後,並不會馬上釋放它所持有的對象鎖,這須要等到它執行完同步代碼塊爲止。code

(d) notifyAll()notify()相似,可是它是通知全部在對象上等待的線程。對象

2.2 實現原理

經過上面的介紹,咱們能夠看到,在整個等待/通知機制當中,線程被掛起時主要有如下三種狀態:等待狀態、超時等待狀態、阻塞狀態,這些狀態都是經過synchroized所修飾的對象來實現的。隊列

在前面咱們介紹synchronized原理的時候,曾經說過每一個對象都會和一個Monitor相關聯,其實每一個Monitor又包含有兩個隊列:等待隊列和同步隊列,其中等待隊列中存放是進入等待狀態的線程,而同步隊列中存放的是等待獲取鎖的線程。ip

下面,咱們經過一段簡單的僞代碼來當即兩個線程的狀態轉換過程:同步

synchronized public void waitThread() {
    //執行a方法.
    wait();
    //執行b方法
}

synchronized public void notifyThread() {
    //執行c方法
    notify();
    //執行d方法
}
複製代碼

咱們有AB兩個線程,咱們模擬如下的一系列行爲:it

(1) A 線程執行 waitThread 方法 此時因爲對象鎖沒有被任何線程持有,所以,A線程成爲對象鎖的持有者:

(2) B 線程執行 notifyThread 方法B線程執行 notifyThread方法時,因爲此時對象鎖已經被 A線程持有,所以它被加入到同步隊列中:
(3) A 線程執行 a 方法
**(4) A 線程執行 wait 方法 ** 當 A線程執行 wait方法後,它會釋放對象鎖,並加入到同步隊列當中,而 B線程則成爲對象鎖新的持有者:
(5) B 線程執行 c 方法
(6) B 線程執行 notify 方法 此時會喚醒等待隊列中 A線程,可是此時 B線程仍然持有對象鎖,所以, A線程只能被加入到同步隊列:
(7) B 線程執行 d 方法
(8) B 線程從 notifyThread 方法返回 此時 A線程從新獲取到對象鎖,所以它被從同步隊列中取出,繼續執行接下來的邏輯:
(9) A 線程執行 b 方法
(10) A 線程從 waitThread 方法中返回A線程從同步方法返回以後,那麼會釋放它所持有的鎖

3、等待/通知的經典範式

對於等待/通知模型,咱們能夠總結出它的經典範式,分別針對等待方和通知方。

3.1 等待方

等待方遵循以下的原則:

  • 獲取對象的鎖
  • 若是條件不知足,那麼調用對象的wait方法,被通知後仍然須要檢查條件
  • 條件知足則繼續執行對應的邏輯

對應的僞代碼爲:

synchronized( 對象 ) {
    while( 條件不知足 ) {
        對象.wait();
    }
    對應的處理邏輯
}
複製代碼

3.2 通知方

通知方遵循以下的原則:

  • 得到對象的鎖
  • 改變條件
  • 通知全部等待在對象上的線程
synchronized( 對象 ) {
    改變條件;
    對象.notifyAll();
}
複製代碼
相關文章
相關標籤/搜索