你好!在學習 JavaRush 中的多執行緒時,您經常會遇到「互斥體」和「監視器」的概念。現在你能不偷看地回答它們有何不同嗎?:) 如果可以的話,幹得好!如果沒有(而且這種情況經常發生)——也就不足為奇了。「互斥體」和「監視器」的概念確實是相關的。而且,在網路上看外部資源上關於多執行緒的講座和影片時,你還會遇到另一個類似的概念—「信號量」。它的功能也很大程度類似於監視器和互斥體。因此,讓我們理解這三個術語,看幾個例子,最後在我們的頭腦中組織對它們之間有何不同的理解:)
為了更好地理解,我們將稍微簡化其術語。想像一下我們有 5 位哲學家需要午餐。同時,我們只有一張桌子,同時不能超過兩個人。我們的任務是養活所有哲學家。他們都不應該挨餓,也不應該在試圖入座時互相「阻擋」(我們必須避免僵局)。這就是我們的哲學家類的樣子:
互斥體
互斥體是用於同步執行緒的特殊物件。它「附加」到 Java 中的每個物件 - 你已經知道了:) 無論你使用標準類還是創建自己的類,都沒有關係,並且Cat
:Dog
所有類的所有物件都有一個互斥體。「mutex」這個名字來自英文“MUTual EXclusion”——“互斥”,這完美地體現了它的目的。正如我們在先前的一講中所說,互斥體的任務就是提供這樣一種機制,使得在某一時刻只有一個執行緒可以存取一個物件。現實生活中互斥體的一個流行的類比是「廁所的例子」。當一個人進入廁所時,他從裡面鎖上門。廁所作為一個可以被多個執行緒存取的物件。廁所門上的鎖是互斥鎖的作用,外面排隊的人是線程的作用。門上的鎖是廁所互斥鎖:它確保一次只有一個人可以進去。 換句話說,一次只有一個執行緒可以處理共享資源。其他執行緒(人)嘗試存取佔用的資源將會失敗。互斥體有幾個重要的特性。 首先,只有兩種狀態是可能的——「空閒」和「忙碌」。這使得更容易理解它的工作原理:可以使用布林變數true/false或二進制數字系統 1/0 來繪製平行線。 其次,國家不能被直接控制。Java 中沒有任何機制可讓您明確取得物件、取得其互斥體並為其指派所需的狀態。換句話說,你不能做這樣的事情:
Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
因此,對象的互斥量無法被釋放。只有Java機器可以直接存取它。程式設計師使用語言工具來處理互斥體。
監視器
監視器是互斥鎖的附加「附加元件」。事實上,監視器是一段對程式設計師「不可見」的程式碼。前面說到互斥鎖,我們舉了一個簡單的例子:public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
synchronized (obj) {
//logic that is only available to one thread at a time
}
}
}
在標有該單字的程式碼區塊中synchronized
,我們的物件的互斥體被捕獲obj
。好吧,捕獲發生了,但是「防禦機制」到底是如何實現的呢?為什麼其他線程看到單字時synchronized
不能進入塊內?是監視器創建了保護機制!編譯器將單字轉換synchronized
成幾個特殊的程式碼片段。讓我們再次回到我們的範例doSomething()
並添加該方法:
public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
//logic that is only available to one thread at a time
synchronized (obj) {
/*выполнить важную работу, при которой доступ к an objectу
должен быть только у одного потока*/
obj.someImportantMethod();
}
}
}
以下是編譯器轉換此程式碼後我們的程式“幕後”將發生的情況:
public class Main {
private Object obj = new Object();
public void doSomething() throws InterruptedException {
//...some logic available to all threads
//логика, которая одновременно доступна только для одного потока:
/*до тех пор, пока мьютекс an object занят -
любой другой поток (кроме того, который его захватил), спит*/
while (obj.getMutex().isBusy()) {
Thread.sleep(1);
}
//пометить мьютекс an object How занятый
obj.getMutex().isBusy() = true;
/*выполнить важную работу, при которой доступ к an objectу
должен быть только у одного потока*/
obj.someImportantMethod();
//освободить мьютекс an object
obj.getMutex().isBusy() = false;
}
}
當然,這個例子不是真的。在這裡,我們嘗試使用類似 Java 的程式碼來反映此時 Java 機器內部發生的情況。然而,這個偽代碼可以很好地理解區塊內的物件和線程實際發生的情況synchronized
,以及編譯器如何將這個詞轉換為幾個對程式設計師「不可見」的命令。本質上,Java 中的監視器是使用單字 來表達的synchronized
。所有出現的程式碼(而不是synchronized
上一個範例中的單字)都是監視器。
訊號
當你自己學習多執行緒時,你會遇到的另一個字是「信號量」。讓我們弄清楚它是什麼以及它與監視器和互斥鎖有何不同。信號量是同步資源存取的一種手段。 它的特殊之處在於它在創建同步機制時使用了計數器。 計數器告訴我們有多少執行緒可以同時存取共享資源。 Java 中的信號量由類別表示Semaphore
。在建立信號量物件時,我們可以使用以下建構函數:
Semaphore(int permits)
Semaphore(int permits, boolean fair)
我們傳遞給建構函數:
-
int permits
— 初始和最大計數器值。即有多少個執行緒可以同時存取一個共享資源; -
boolean fair
- 建立執行緒接收存取的順序。如果fair
= true,則按照等待執行緒請求的順序授予存取權限。如果為false,則順序將由執行緒調度程序決定。
class Philosopher extends Thread {
private Semaphore sem;
// поел ли философ
private boolean full = false;
private String name;
Philosopher(Semaphore sem, String name) {
this.sem=sem;
this.name=name;
}
public void run()
{
try
{
// если философ еще не ел
if (!full) {
//Запрашиваем у семафора разрешение на выполнение
sem.acquire();
System.out.println (name + " садится за стол");
// философ ест
sleep(300);
full = true;
System.out.println (name + " поел! Он выходит из-за стола");
sem.release();
// философ ушел, освободив место другим
sleep(300);
}
}
catch(InterruptedException e) {
System.out.println ("What-то пошло не так!");
}
}
}
這是運行我們的程式的程式碼:
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore(2);
new Philosopher(sem,"Сократ").start();
new Philosopher(sem,"Платон").start();
new Philosopher(sem,"Аристотель").start();
new Philosopher(sem,"Фалес").start();
new Philosopher(sem,"Пифагор").start();
}
}
我們創建了一個計數為2的信號量來滿足只有兩個哲學家可以同時吃飯的條件。也就是說,只有兩個執行緒可以同時工作,因為我們的類別Philosopher
是繼承自Thread
!類別acquire()
和方法控制其權限計數器。此方法請求從信號量存取資源的權限。如果 counter > 0,則授予權限,且計數器減 1。此方法「釋放」先前授予的權限並將其返回計數器(將信號量的授予計數器加 1)。當我們運行程式時我們會得到什麼?問題解決了嗎?我們的哲學家會在等待輪到他們的時候打架嗎?:) 這是我們收到的控制台輸出: 蘇格拉底坐在桌子旁 柏拉圖坐在 蘇格拉底吃過的桌子旁!他離開了桌子。柏拉圖已經吃過了!他離開桌子 亞里斯多德在桌子旁坐下 畢達哥拉斯在 亞里斯多德吃過的桌子旁坐下!他離開了畢達哥拉斯吃過的桌子!他離開了泰爾斯的桌子,坐在了泰爾斯吃過的 桌子旁邊 !他離開了桌子, 我們成功了!儘管泰勒斯必須獨自用餐,但我認為他並沒有生我們的氣:) 您可能已經注意到互斥體和信號量之間的一些相似之處。 一般來說,它們有相同的目的:同步對某些資源的存取。 唯一的差異是物件的互斥體一次只能由一個執行緒獲取,而信號量則使用執行緒計數器,其中多個執行緒可以同時存取該資源。這不只是巧合的相似之處:) 事實上,互斥鎖是一個單位信號量。也就是說,它是一個計數器初始設定為1的信號量。它也被稱為“二進制信號量”,因為它的計數器只能有2個值——1(“空閒”)和0(“忙”)。就這樣!正如您所看到的,一切都變得不再那麼混亂了:) 現在,如果您想在 Internet 上更詳細地研究多線程主題,那麼您會更容易理解這些概念。下一課見! release()
Semaphore
acquire()
release()
GO TO FULL VERSION