CodeGym /Java Blog /Toto sisi /互斥量、監視器和信號量之間的區別
John Squirrels
等級 41
San Francisco

互斥量、監視器和信號量之間的區別

在 Toto sisi 群組發布
你好!當您在 CodeGym 上學習多線程時,您經常會遇到“互斥”和“監視器”的概念。不用偷看,你能說出它們有何不同嗎?:) 如果是,幹得好!如果不是(這是最常見的),那就不足為奇了。“Mutex”和“monitor”其實是相關的概念。此外,當您在其他網站上閱讀有關多線程的課程和觀看視頻時,您會遇到另一個類似的概念:“信號量”。它還具有與監視器和互斥鎖非常相似的功能。這就是我們要研究這三個術語的原因。我們將通過幾個示例來明確理解這些概念之間的區別 :)

互斥鎖

互斥量(或鎖)是一種用於同步線程的特殊機制。一個是“附加”到 Java 中的每個對象 — 您已經知道 :) 使用標準類或創建自己的類都沒有關係,例如CatDog所有類的所有對像都有一個 mutex。“mutex”一詞來自“MUTual EXclusion”,完美地描述了它的用途。正如我們在之前的一節課中所說的那樣,互斥量可以確保一次只有一個線程可以訪問該對象。 互斥量的一個流行的現實生活示例涉及廁所。當一個人進入廁所隔斷時,他從裡面鎖上門。廁所就像一個可以被多個線程訪問的對象。隔斷門上的鎖就像一個互斥量,外面的人排隊代表線程。門上的鎖是廁所的互斥鎖:它確保只有一個人可以進入。 互斥量、監視器和信號量之間有什麼區別? - 2換句話說,一次只有一個線程可以使用共享資源。其他線程(人)嘗試訪問已佔用的資源將失敗。互斥鎖有幾個重要的特性。 首先,只有兩種狀態是可能的:“解鎖”和“鎖定”。這有助於我們理解它是如何工作的:您可以與布爾變量 (true/false) 或二進制數 (0/1) 進行比較。 , 狀態不能被直接控制。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 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互斥量、監視器和信號量之間有什麼區別? - 3類表示。在創建信號量對象時,我們可以使用以下構造函數:

Semaphore(int permits)
Semaphore(int permits, boolean fair)
我們將以下內容傳遞給構造函數:
    int permits — 計數器的初始值和最大值。也就是說,這個參數決定了有多少線程可以同時訪問共享資源;
  • boolean fair — 確定線程獲得訪問權限的順序。如果fair為真,則按照等待線程請求的順序授予訪問權限。如果為假,則順序由線程調度程序確定。
信號量使用的一個典型例子是哲學家就餐問題。 互斥量、監視器和信號量之間有什麼區別? - 4為了便於理解,我們稍微簡化一下。想像一下,我們有 5 位哲學家需要吃午飯。此外,我們有一張桌子最多只能容納兩個人。我們的任務是養活所有的哲學家。他們都不應該餓著肚子,他們都不應該在試圖坐在桌子旁時互相“阻擋”(我們必須避免僵局)。這是我們的哲學家課程的樣子:

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類繼承了ThreadSemaphore類的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 不得不單獨用餐,但我認為我們沒有冒犯他 :) 您可能已經註意到互斥量和信號量之間的一些相似之處。 實際上,它們具有相同的使命:同步對某些資源的訪問。 互斥量、監視器和信號量之間有什麼區別? - 5唯一的區別是一個對象的互斥量一次只能由一個線程獲取,而在使用線程計數器的信號量的情況下,多個線程可以同時訪問該資源。這不僅僅是巧合 :) 互斥鎖實際上是一個信號量計數為 1。換句話說,它是一個可以容納單個線程的信號量。它也被稱為“二進制信號量”,因為它的計數器只能有 2 個值——1(“解鎖”)和 0(“鎖定”)。就是這樣!如您所見,它並沒有那麼令人困惑 :) 現在,如果您想在 Internet 上更詳細地研究多線程,您將更容易理解這些概念。下節課見!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION