Java - 重入監視器

你好,未來的Java法師們!今天,我們將踏上一段令人興奮的旅程,探索Java世界中的重入監視器。如果你是編程新手,別擔心——我將成為你的友好導遊,我們會一步步來。所以,拿起你的虛擬魔杖(鍵盤),讓我們一起深入探討吧!

Java - Reentrant Monitor

什麼是重入監視器?

在我們深入細節之前,讓我們先了解什麼是重入監視器。想像你在一個神奇的圖書館裡,這個圖書館一次只能讓一個人進入特定的區域。現在,如果你已經在那個區域裡,並且需要進入更深一層的子區域呢?重入監視器就像是一張神奇的通行證,讓你可以做到這一點——進入你已經在其中的區域!

在Java術語中,重入監視器允許已經持有鎖的線程再次獲取同一個鎖而不會被阻塞。這就像給自己准許進入你已經在其中的房間。很棒吧?

我們為什麼需要重入監視器?

你可能會想,"我們為什麼需要這張神奇的通行證?" 在多線程的世界中(程序的多個部分同時運行),我們經常需要保護共享資源。重入監視器能夠幫助我們更有效地做到這一點,特別是我們有調用其他也需要相同鎖的方法時。

瀑布鎖的介紹

Java為我們提供了一個名為ReentrantLock的類來實現重入監視器。這就像我們的神奇圖書館通行證,但在代碼形式!

語法

這是我們如何創建和使用ReentrantLock的方法:

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

// 為了鎖定
lock.lock();
try {
// 你的受保護代碼在這裡
} finally {
// 為了解鎖
lock.unlock();
}

別擔心這看起來有點令人生畏。我們會通過一些例子來分解它!

沒有重入鎖的多線程

讓我們從一個不使用ReentrantLock的簡單例子開始。想像我們有一個神奇的計數器,多個法師(線程)試圖增加它:

public class MagicalCounter {
private int count = 0;

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

現在,讓我們創建一些法師線程來增加這個計數器:

public class WizardThread extends Thread {
private MagicalCounter counter;

public WizardThread(MagicalCounter counter) {
this.counter = counter;
}

public void run() {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}

public class MagicalCounterTest {
public static void main(String[] args) throws InterruptedException {
MagicalCounter counter = new MagicalCounter();
WizardThread wizard1 = new WizardThread(counter);
WizardThread wizard2 = new WizardThread(counter);

wizard1.start();
wizard2.start();

wizard1.join();
wizard2.join();

System.out.println("最終計數: " + counter.getCount());
}
}

如果你運行這個程序,你可能會期望最終計數為2000(每個法師增加1000次)。但結果往往小於2000。這是因為我們的法師們互相踩腳——他們試圖同時增加計數器,導致增加的次數丟失。

有重入鎖的多線程

現在,讓我們在我們的計數器上灑一點ReentrantLock的魔法:

import java.util.concurrent.locks.ReentrantLock;

public class MagicalCounterWithLock {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
return count;
}
}

讓我們分解一下:

  1. 我們創建一個名為lockReentrantLock對象。
  2. increment方法中,我們在增加計數器之前調用lock.lock()
  3. 我們使用try-finally語句來確保我們總是解鎖,即使發生異常。
  4. 增加後,我們在finally語句中調用lock.unlock()

現在,如果我們使用這個新的MagicalCounterWithLock運行我們的WizardThread測試,我們總是會得到2000作為最終計數。我們的法師們現在會很好地輪流!

重入鎖的真正公平性

ReentrantLock還有一個小花招。我們可以為它創建一個公平性參數:

ReentrantLock fairLock = new ReentrantLock(true);

當我們將公平性設為true時,鎖會優先授予等待時間最長的線程。這就像為我們的法師們排成一個正規的隊伍!

這是我們如何使用它的方法:

public class FairMagicalCounter {
private int count = 0;
private ReentrantLock fairLock = new ReentrantLock(true);

public void increment() {
fairLock.lock();
try {
count++;
} finally {
fairLock.unlock();
}
}

public int getCount() {
return count;
}
}

這樣可以確保,如果有多个法師等待增加計數器,等待時間最長的法師會先進行。

結論

就是这样,年輕的法師們!我們已經穿越了Java世界中的重入監視器這個神奇的世界。我們看到了他們如何在多線程環境中幫助我們管理共享資源,確保我們的神奇計數器(和其他共享對象)能夠正確增加。

記住,就像任何強大的魔法一樣,重入監視器應該明智地使用。它們很適合管理對共享資源的並發訪問,但過度使用可能會導致性能下降,甚至死鎖(法師們永遠等待對方的鎖的情況)!

練習這些咒語……呃,代碼示例,很快你就能像專家一樣施展多線程魔法!快樂編程,願你的線程總是和諧!

Credits: Image by storyset