你好!當您在 CodeGym 上學習多線程時,您經常會遇到“互斥”和“監視器”的概念。不用偷看,你能說出它們有何不同嗎?:) 如果是,幹得好!如果不是(這是最常見的),那就不足為奇了。“Mutex”和“monitor”其實是相關的概念。此外,當您在其他網站上閱讀有關多線程的課程和觀看視頻時,您會遇到另一個類似的概念:“信號量”。它還具有與監視器和互斥鎖非常相似的功能。這就是我們要研究這三個術語的原因。我們將通過幾個示例來明確理解這些概念之間的區別 :)
換句話說,一次只有一個線程可以使用共享資源。其他線程(人)嘗試訪問已佔用的資源將失敗。互斥鎖有幾個重要的特性。 首先,只有兩種狀態是可能的:“解鎖”和“鎖定”。這有助於我們理解它是如何工作的:您可以與布爾變量 (true/false) 或二進制數 (0/1) 進行比較。 , 狀態不能被直接控制。Java 沒有任何機制可以讓您顯式獲取對象、獲取其互斥量並分配所需的狀態。換句話說,你不能做這樣的事情:
類表示。在創建信號量對象時,我們可以使用以下構造函數:
為了便於理解,我們稍微簡化一下。想像一下,我們有 5 位哲學家需要吃午飯。此外,我們有一張桌子最多只能容納兩個人。我們的任務是養活所有的哲學家。他們都不應該餓著肚子,他們都不應該在試圖坐在桌子旁時互相“阻擋”(我們必須避免僵局)。這是我們的哲學家課程的樣子:
唯一的區別是一個對象的互斥量一次只能由一個線程獲取,而在使用線程計數器的信號量的情況下,多個線程可以同時訪問該資源。這不僅僅是巧合 :) 互斥鎖實際上是一個信號量計數為 1。換句話說,它是一個可以容納單個線程的信號量。它也被稱為“二進制信號量”,因為它的計數器只能有 2 個值——1(“解鎖”)和 0(“鎖定”)。就是這樣!如您所見,它並沒有那麼令人困惑 :) 現在,如果您想在 Internet 上更詳細地研究多線程,您將更容易理解這些概念。下節課見!
互斥鎖
互斥量(或鎖)是一種用於同步線程的特殊機制。一個是“附加”到 Java 中的每個對象 — 您已經知道 :) 使用標準類或創建自己的類都沒有關係,例如Cat和Dog:所有類的所有對像都有一個 mutex。“mutex”一詞來自“MUTual EXclusion”,完美地描述了它的用途。正如我們在之前的一節課中所說的那樣,互斥量可以確保一次只有一個線程可以訪問該對象。 互斥量的一個流行的現實生活示例涉及廁所。當一個人進入廁所隔斷時,他從裡面鎖上門。廁所就像一個可以被多個線程訪問的對象。隔斷門上的鎖就像一個互斥量,外面的人排隊代表線程。門上的鎖是廁所的互斥鎖:它確保只有一個人可以進入。
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 for all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
在標有synchronized關鍵字 的代碼塊中,獲取了我們obj對象的mutex 。太好了,我們可以獲得鎖,但是“保護”究竟是如何提供的呢?當我們看到synchronized一詞時,是什麼阻止其他線程進入塊?保護來自監視器!編譯器將synchronized關鍵字轉換成幾段特殊的代碼。再一次,讓我們回到我們使用doSomething()方法的示例。我們將添加到它:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...some logic, available for all threads
// Logic available to just one thread at a time
synchronized (obj) {
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
}
}
}
以下是編譯器轉換此代碼後“幕後”發生的情況:
public class Main {
private Object obj = new Object();
public void doSomething() throws InterruptedException {
// ...some logic, available for all threads
// Logic available to just one thread at a time:
/* as long as the object's mutex is busy,
all the other threads (except the one that acquired it) are put to sleep */
while (obj.getMutex().isBusy()) {
Thread.sleep(1);
}
// Mark the object's mutex as busy
obj.getMutex().isBusy() = true;
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
// Free the object's mutex
obj.getMutex().isBusy() = false;
}
}
當然,這不是一個真實的例子。在這裡,我們使用類似 Java 的代碼來描述 Java 機器內部發生的事情。也就是說,這段偽代碼很好地理解了同步塊內的對象和線程實際發生了什麼,以及編譯器如何將這個關鍵字轉換成幾個對程序員“不可見”的語句。基本上,Java 使用synchronized關鍵字來表示監視器。上例中出現的所有代替synchronized關鍵字的代碼都是monitor。
信號
在個人學習多線程時會遇到的另一個詞是“信號量”。讓我們弄清楚這是什麼以及它與監視器和互斥量有何不同。信號量是一種用於同步訪問某些資源的工具。 它的顯著特點是它使用計數器來創建同步機制。 計數器告訴我們有多少線程可以同時訪問共享資源。 Java 中的信號量由Semaphore
Semaphore(int permits)
Semaphore(int permits, boolean fair)
我們將以下內容傳遞給構造函數:
- boolean fair — 確定線程獲得訪問權限的順序。如果fair為真,則按照等待線程請求的順序授予訪問權限。如果為假,則順序由線程調度程序確定。

class Philosopher extends Thread {
private Semaphore sem;
// Did the philosopher eat?
private boolean full = false;
private String name;
Philosopher(Semaphore sem, String name) {
this.sem=sem;
this.name=name;
}
public void run()
{
try
{
// If the philosopher has not eaten
if (!full) {
// Ask the semaphore for permission to run
sem.acquire();
System.out.println(name + " takes a seat at the table");
// The philosopher eats
sleep(300);
full = true;
System.out.println(name + " has eaten! He leaves the table");
sem.release();
// The philosopher leaves, making room for others
sleep(300);
}
}
catch(InterruptedException e) {
System.out.println("Something went wrong!");
}
}
}
這是運行我們程序的代碼:
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore(2);
new Philosopher(sem, "Socrates").start();
new Philosopher(sem,"Plato").start();
new Philosopher(sem,"Aristotle").start();
new Philosopher(sem, "Thales").start();
new Philosopher(sem, "Pythagoras").start();
}
}
我們創建了一個信號量,其計數器設置為 2 以滿足條件:只有兩個哲學家可以同時進餐。也就是只能同時運行兩個線程,因為我們的Philosopher類繼承了Thread!Semaphore類的acquire ()和release()方法控制其訪問計數器。acquire() 方法請求信號量訪問資源。如果計數器 >0,則授予訪問權限並將計數器減 1。release ()方法“釋放”先前授予的訪問權限,將其返回給計數器(將信號量的訪問計數器增加 1)。當我們運行程序時我們得到了什麼?問題解決了嗎?我們的哲學家在等待輪到他們的時候不會戰鬥嗎?:) 這是我們得到的控制台輸出:
Socrates takes a seat at the table
Plato takes a seat at the table
Socrates has eaten! He leaves the table
Plato has eaten! He leaves the table
Aristotle takes a seat at the table
Pythagoras takes a seat at the table
Aristotle has eaten! He leaves the table
Pythagoras has eaten! He leaves the table
Thales takes a seat at the table
Thales has eaten! He leaves the table
我們做到了!儘管 Thales 不得不單獨用餐,但我認為我們沒有冒犯他 :) 您可能已經註意到互斥量和信號量之間的一些相似之處。 實際上,它們具有相同的使命:同步對某些資源的訪問。 
GO TO FULL VERSION