你好!今天我們將繼續考慮多線程編程的特性並討論線程同步。
Java 中的同步是什麼?
在編程領域之外,它意味著允許兩個設備或程序一起工作的安排。例如,智能手機和計算機可以與 Google 帳戶同步,網站帳戶可以與社交網絡帳戶同步,以便您可以使用它們進行登錄。線程同步具有類似的含義:它是線程與線程交互的一種安排彼此。在之前的課程中,我們的線程彼此分開生活和工作。一個執行計算,第二個睡覺,第三個在控制台上顯示一些東西,但他們沒有互動。在實際程序中,這種情況很少見。多個線程可以主動處理和修改同一個數據集。這會產生問題。想像一下,多個線程將文本寫入同一個地方,例如,文本文件或控制台。在這種情況下,文件或控制台成為共享資源。線程不知道彼此的存在,因此它們只是在線程調度程序分配給它們的時間內寫入它們可以寫入的所有內容。在最近的一節課中,我們看到了一個例子,說明了這會導致什麼結果。現在讓我們回憶一下: 原因在於線程正在使用共享資源(控制台),而沒有相互協調它們的操作。如果線程調度程序將時間分配給 Thread-1,它會立即將所有內容寫入控制台。其他線程已經或尚未設法寫入的內容無關緊要。如您所見,結果令人沮喪。這就是為什麼他們在多線程編程中引入了一個特殊的概念,互斥量(互斥)。 互斥量的目的就是提供一種機制,讓某個時間只有一個線程可以訪問一個對象。如果 Thread-1 獲得對象 A 的互斥鎖,其他線程將無法訪問和修改該對象。其他線程必須等到對象 A 的互斥體被釋放。這是生活中的一個例子:假設您和其他 10 個陌生人正在參加一項練習。輪流,你需要表達你的想法並討論一些事情。但因為你們是第一次見面,為了不經常打斷對方而大發雷霆,你們使用了“會說話的球”:只有拿球的人才能說話。通過這種方式,您最終會進行良好而富有成果的討論。本質上,球是一個互斥量。如果一個對象的互斥鎖在一個線程手中,其他線程就不能使用該對象。Object
類,這意味著 Java 中的每個對像都有一個。
同步運算符是如何工作的
讓我們認識一個新的關鍵字:synchronized。它用於標記某個代碼塊。如果代碼塊標有synchronized
關鍵字,則該代碼塊一次只能由一個線程執行。可以用不同的方式實現同步。例如,通過聲明要同步的整個方法:
public synchronized void doSomething() {
// ...Method logic
}
或者編寫一個代碼塊,其中使用某個對象執行同步:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
意思很簡單。如果一個線程進入標有synchronized
關鍵字的代碼塊,它會立即捕獲對象的互斥體,所有其他試圖進入同一塊或方法的線程都被迫等待,直到前一個線程完成其工作並釋放監視器。 順便一提!在課程中,您已經看過 的示例synchronized
,但它們看起來有所不同:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
這個話題對你來說是新的。而且,當然,語法上會有混淆。因此,請立即記住它,以免以後因不同的書寫方式而感到困惑。這兩種寫法意思是一樣的:
public void swap() {
synchronized (this)
{
// ...Method logic
}
}
public synchronized void swap() {
}
}
在第一種情況下,您將在進入方法後立即創建一個同步代碼塊。它由對象同步this
,即當前對象。在第二個示例中,您將synchronized
關鍵字應用於整個方法。這使得無需顯式指示用於同步的對象。由於整個方法都標有關鍵字,因此該方法將自動為類的所有實例同步。我們不會深入討論哪種方式更好。現在,選擇你最喜歡的方式:) 最主要的是要記住:只有當它的所有邏輯一次由一個線程執行時,你才能聲明一個方法同步。例如,將以下doSomething()
方法同步是錯誤的:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
如您所見,部分方法包含不需要同步的邏輯。該代碼可以同時由多個線程運行,並且所有關鍵位置都設置在一個單獨的synchronized
塊中。還有一件事。讓我們仔細檢查名稱交換課程中的示例:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
注意:同步是使用this
. 即,使用特定的MyClass
對象。假設我們有 2 個線程(Thread-1
和Thread-2
)並且只有一個MyClass myClass
對象。在這種情況下,如果Thread-1
調用該myClass.swap()
方法,對象的互斥量將處於忙碌狀態,並且在嘗試調用該myClass.swap()
方法Thread-2
將掛起,等待互斥量被釋放。如果我們有 2 個線程和 2 個MyClass
對象(myClass1
和myClass2
),我們的線程可以輕鬆地同時對不同對象執行同步方法。第一個線程執行這個:
myClass1.swap();
第二個執行這個:
myClass2.swap();
在這種情況下,方法synchronized
中的關鍵字swap()
不會影響程序的運行,因為同步是使用特定對象執行的。在後一種情況下,我們有 2 個對象。因此,線程不會互相造成問題。畢竟,兩個對像有 2 個不同的互斥量,獲取一個與獲取另一個是獨立的。
靜態方法中同步的特殊功能
但是如果你需要同步一個靜態方法呢?
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static synchronized void swap() {
String s = name1;
name1 = name2;
name2 = s;
}
}
目前尚不清楚互斥鎖在這裡扮演什麼角色。 畢竟,我們已經確定每個對像都有一個互斥量。但問題是我們不需要對象來調用方法MyClass.swap()
:方法是靜態的!下一個是什麼?:/ 這裡其實沒有問題。Java 的創造者處理了一切 :) 如果包含關鍵並發邏輯的方法是靜態的,那麼同步將在類級別執行。為了更清楚,我們可以重寫上面的代碼如下:
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static void swap() {
synchronized (MyClass.class) {
String s = name1;
name1 = name2;
name2 = s;
}
}
}
原則上,您自己可能已經想到了這一點:因為沒有對象,同步機制必須以某種方式嵌入到類本身中。就是這樣:我們可以使用類來同步。
GO TO FULL VERSION