可能在synchronized關鍵字的實現原理中,你已經知道了它的底層是使用Monitor的相關指令來實現的,但是還不清楚Monitor的具體細節。本文將讓你徹底Monitor的底層實現原理。
管程
一個管程可以被認為是一個帶有特殊房間的建築,這個特殊房間只能被一個執行緒佔用。這個房間包含很多資料和程式碼。
如果一個執行緒要佔用特殊房間(也就是紅色區域),那麼首先它必須在Hallway中等待。排程器基於某些規則(例如先進先出)從Hallway中取一個執行緒。如果執行緒在Hallway由於某些原因被掛起,它將會被送往等待房間(也就是藍色區域),在一段時間後被排程到特殊房間中。
簡而言之,監視器是一種監視現場訪問特殊房間的裝置。他能夠使有且僅有一個執行緒訪問的受保護的程式碼和資料。
Monitor
在Java虛擬機器中,每一個物件和類都與一個監視器相關聯。為了實現監視器的互斥功能,鎖(有時候也稱為互斥體)與每一個物件和類關聯。在作業系統書中,這叫做訊號量,互斥鎖也被稱為二元訊號量。
如果一個執行緒擁有某些資料上的鎖,其他執行緒想要獲得鎖只能等到這個執行緒釋放鎖。如果我們在進行多執行緒程式設計時總是需要編寫一個訊號量,那就不太方便了。幸運的是,我們不需要這樣做,因為JVM會自動為我們做這件事。
為了宣告一個同步區域(這裡意味著資料不可能被超過一個執行緒訪問),Java提供了synchronized塊和synchronized方法。一旦程式碼被synchronized關鍵字繫結,它就是一個監視器區域。它的鎖將會在後面被JVM實現。
Monitor是 Java中用以實現執行緒之間的互斥與協作的主要手段,它可以看成是物件或者Class的鎖。每一個物件都有,也僅有一個 monitor。下面這個圖,描述了執行緒和 Monitor之間關係,以及執行緒的狀態轉換圖:
進入區(Entrt Set):表示執行緒通過synchronized要求獲取物件的鎖,但並未得到。
擁有者(The Owner):表示執行緒成功競爭到物件鎖。
等待區(Wait Set):表示執行緒通過物件的wait方法,釋放物件的鎖,並在等待區等待被喚醒。
執行緒狀態
-
NEW,未啟動的。不會出現在Dump中。
-
RUNNABLE,在虛擬機器內執行的。
-
BLOCKED,等待獲得監視器鎖。
-
WATING,無限期等待另一個執行緒執行特定操作。
-
TIMED_WATING,有時限的等待另一個執行緒的特定操作。
-
TERMINATED,已退出的。
舉個例子:
package com.jiuyan.mountain.test;
import java.util.concurrent.TimeUnit;
/**
* Hello world!
*
*/
public class App {
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
Thread t1 = new Thread(task);
t1.setName("t1");
Thread t2 = new Thread(task);
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyTask implements Runnable {
private Integer mutex;
public MyTask() {
mutex = 1;
}
@Override
public void run() {
synchronized (mutex) {
while(true) {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
執行緒狀態:
"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuyan.mountain.test.MyTask.run(App.java:35)
- waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:745)
"t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
t1沒有搶到鎖,所以顯示BLOCKED。t2搶到了鎖,但是處於睡眠中,所以顯示TIMED_WAITING,有限等待某個條件來喚醒。
把睡眠的程式碼去掉,執行緒狀態變成了:
"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuyan.mountain.test.MyTask.run(App.java:35)
- waiting to lock <0x0000000784206650> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:745)
"t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
t1顯示RUNNABLE,說明正在執行,這裡需要額外說明一下,如果這個執行緒正在查詢資料庫,但是資料庫發生死鎖,雖然執行緒顯示在執行,實際上並沒有工作,對於IO型的執行緒別隻用執行緒狀態來判斷工作是否正常。
把MyTask的程式碼小改一下,執行緒拿到鎖之後執行wait,釋放鎖,進入等待區。
public void run() {
synchronized (mutex) {
if(mutex == 1) {
try {
mutex.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執行緒狀態如下:
"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
"t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
兩個執行緒都顯示WAITING,這次是無限期的,需要重新獲得鎖,所以後面跟了on object monitor。
再來個死鎖的例子:
package com.jiuyan.mountain.test;
import java.util.concurrent.TimeUnit;
/**
* Hello world!
*
*/
public class App {
public static void main(String[] args) throws InterruptedException {
MyTask task1 = new MyTask(true);
MyTask task2 = new MyTask(false);
Thread t1 = new Thread(task1);
t1.setName("t1");
Thread t2 = new Thread(task2);
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyTask implements Runnable {
private boolean flag;
public MyTask(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized (Mutex.mutex1) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Mutex.mutex2) {
System.out.println("ok");
}
}
} else {
synchronized (Mutex.mutex2) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Mutex.mutex1) {
System.out.println("ok");
}
}
}
}
}
class Mutex {
public static Integer mutex1 = 1;
public static Integer mutex2 = 2;
}
執行緒狀態:
"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuyan.mountain.test.MyTask.run(App.java:55)
- waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer)
- locked <0x00000007d6c45be8> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:745)
"t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuyan.mountain.test.MyTask.run(App.java:43)
- waiting to lock <0x00000007d6c45be8> (a java.lang.Integer)
- locked <0x00000007d6c45bd8> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:745)
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer),
which is held by "t2"
這個有點像哲學家就餐問題,每個執行緒都持有對方需要的鎖,那就執行不下去了。
最後
私信回覆 資料 領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文件的Java核心知識點總結!
這些資料的內容都是面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多執行緒併發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java演算法、資料庫、Zookeeper、分散式快取、資料結構等等。
作者:monitor