synchronized
synchronized使用起來非常的方便,但是方便不等於簡單,裏面涉及的知識點還是挺多的,這裏簡單記錄一點筆記。
首先記錄一下我所認識的鎖,任何對象在頭信息裏都有一個鎖標記,類也是特殊的對象(class對象),同一時刻只能有一個線程能持有這個鎖,當用synchronized時,線程會獲取鎖,別的線程想要獲取這個鎖時只能等待這個線程釋放鎖。
這兩天看了很多人的文章,講的都大同小異,說到synchronized的用法,有的說三種,有的說四種,有的說五種,也不知道官方是怎麼定義的,我就把所有的都羅列一下:
鎖代碼塊 | synchronized(this) | 對象鎖 |
synchronized(object) | 對象鎖 | |
synchronized(class) | 類鎖 | |
鎖方法 | synchronized void method() | 對象鎖 |
static synchronized void method() | 類鎖 |
同一個對象鎖/類鎖,同一時刻只能被一個線程獲取。對於小白玩家,這些就是大概了,再多的瞭解可以看《深入理解Java虛擬機》、《Java編程思想》等書,https://www.cnblogs.com/zaizhoumo/p/7700161.html 這篇博客也不錯。
wait、notify、notifyAll
這三個一般都放在一塊兒說,因爲都是在synchronized的同步塊中使用的。
鎖的管理是通過Monitor來完成的,Monitor維護着WaitSet和EntryList、owner。WaitSet裏的線程必須被notify/notifyAll喚醒,才能進入EntryList,EntryList中的線程準備爭奪鎖並等待鎖被釋放,owner是當前獲得鎖的線程,owner的線程在wait()方法之後會進入WaitSet。所以這三個狀態是互相轉換的。
notify和notifyAll的區別從字面可以理解,notify是隻喚醒一個WaitSet中的線程進入EntryList,notifyAll是喚醒全部WaitSet中的線程進入EntryList。假設WaitSet中有5個線程等待被喚醒,notify之後,WaitSet中還剩4個線程,被喚醒的線程參與鎖的競爭;notifyAll之後,WaitSet中的線程全部被喚醒,參與鎖的競爭。
小tips:永遠在循環中調用wait()
wait之後的線程被誰喚醒是不可知的,假設一個場景:“多線程生產,多線程消費”。
synchronized(this) {
if (empty) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
remove();
notifyAll();
}
synchronized(this) {
while(empty) {//被喚醒後再次檢查條件
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
remove();
notifyAll();
}
這裏有兩段代碼,一個是if判斷,一個是while循環判斷。消費者發現empty條件成立時會進入等待,因爲生產者和消費者都是多個線程,如果喚醒這個消費者線程的是別的消費者線程,那麼第一段代碼就會走到remove()方法,可能會出現意料之外的異常。而第二段代碼在線程被喚醒後還會再次判斷,保證了remove()的前置條件。