“你好,阿米戈!我們有靈丹妙藥——包治百病。正如我們已經看到的,不受控制的線程切換是一個問題。”

“為什麼線程自己不能決定何時切換到下一個線程?做他們需要做的一切,然後發出信號,«我完成了!»?”

“讓線程自己控制切換將是一個更大的問題。假設你有一些寫得不好的代碼,並且線程永遠不會交出 CPU。在過去,這就是它的工作方式。這簡直是一場噩夢。”

“好吧。那麼解決辦法是什麼?”

阻塞其他線程。 這就是它的工作原理。”

很明顯,線程在嘗試使用共享對象和/或資源時會相互干擾。正如我們在控制台輸出示例中看到的那樣:有一個控制台,所有線程都輸出到它。很亂。

所以發明了一個特殊的對象:mutex。這就像浴室門上的標語,上面寫著«available / occupied»。它有兩種狀態:對象可用已佔用。這些狀態也稱為“鎖定”和“解鎖”。

當一個線程需要一個與其他線程共享的對象時,它會檢查與該對象關聯的互斥體。如果互斥鎖被解鎖,那麼線程會鎖定它(將其標記為“已佔用”)並開始使用共享資源。線程完成其業務後,互斥鎖被解鎖(標記為“可用”)。

如果線程想要使用該對象並且互斥鎖被鎖定,則線程在等待時休眠。當互斥量最終被佔用線程解鎖時,我們的線程會立即鎖定它並開始運行。與浴室門標誌的類比是完美的。

“那麼我如何使用互斥鎖?我需要創建特殊對象嗎?”

“它比那簡單得多。Java的創建者將這個互斥鎖構建到 Object 類中。所以你甚至不必創建它。它是每個對象的一部分。下面是它的工作原理:”

代碼 描述
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap 方法交換 name1 和 name2 變量的值。

如果同時從兩個線程調用它會發生什麼?

實際代碼執行 第一個線程的代碼 第二個線程的代碼
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//other thread is running
name2 = s1;
//the thread waits until the mutex is unlocked

String s2 = name1;
name1 = name2;
//other thread is running
//other thread is running
name2 = s2;
底線
變量的值被交換了兩次,回到了原來的位置。

注意關鍵字 synchronized

“是啊,什麼意思?”

“當一個線程進入一個標記為同步的代碼塊時,Java 機器立即鎖定同步一詞後括號中指示的對象的互斥量。在我們的線程離開之前,其他線程不能進入這個塊。一旦我們的線程離開標記為同步的塊,互斥鎖立即自動解鎖,可供另一個線程獲取。”

如果互斥量被佔用,那麼我們的線程將停止並等待它釋放。

“如此簡單,如此優雅。這是一個漂亮的解決方案。”

“是的。但是你認為在這種情況下會發生什麼?”

代碼 描述
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap 和 swap2 方法共享相同的互斥體this對象)。

如果一個線程調用 swap 方法而另一個線程調用 swap2 方法會怎樣?

“由於互斥鎖是相同的,第二個線程必須等到第一個線程離開同步塊。所以並發訪問不會有任何問題。”

“幹得好,阿米戈!這是正確答案!”

現在我想指出,synchronized不僅可以用來標記代碼塊,還可以用來標記方法。這是什麼意思:

代碼 到底發生了什麼
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}