【多線程】synchonized 實現過程
synchronized
關鍵字是 Java 中用於實現線程同步的一種機制,旨在確保多個線程在執行同步代碼塊時的原子性和可見性。synchronized
可以用於方法或者代碼塊上,以確保同一時刻只有一個線程可以執行被同步的代碼。
synchronized 的實現過程
1. 鎖的獲取和釋放
-
• 方法級的同步:當一個方法被
synchronized
修飾時,它鎖定的是調用該方法的對象實例(如果是 static 方法,則鎖定的是該類的 Class 對象)。每次調用該方法的時候,都會檢查是否有其他線程持有這個對象的鎖。如果有,則當前線程進入阻塞狀態;如果沒有,則當前線程持有該鎖,繼續執行方法。 -
• 同步塊:
synchronized
也可以用來同步一個特定的代碼塊而不是整個方法。這種情況下,它允許更細粒度的鎖控制。同步塊需要指定一個鎖對象,當線程進入同步塊之前,需要獲取這個對象的鎖;退出同步塊時,釋放鎖。
2. 底層實現
在 JVM 層面,synchronized
關鍵字的實現依賴於內部的鎖機制(也稱爲監視器鎖或互斥鎖)。Java 的每個對象都有一個與之關聯的監視器鎖。
-
• 進入同步塊 / 方法:
-
• 線程試圖獲取對象的鎖(對於實例方法,鎖是持有該方法的對象實例;對於靜態方法,鎖是該類的 Class 對象;對於同步塊,鎖是指定的對象)。
-
• 如果鎖被另一個線程持有,則當前線程被阻塞,直到鎖變得可用。
-
• 一旦獲得鎖,線程就可以執行同步代碼。
-
• 退出同步塊 / 方法:
-
• 持有鎖的線程執行完同步代碼後,會釋放鎖。
-
• 其他被阻塞的線程中會有一個線程獲取到鎖,然後繼續執行。
3. 鎖的升級和優化
從 JDK 1.6 開始,JVM 對 synchronized
關鍵字實現進行了大量的優化,包括鎖的升級策略,以減少鎖的競爭所導致的系統開銷。
-
• 輕量級鎖:當鎖對象第一次被線程鎖定時,它不會立即進入重量級鎖。JVM 會將鎖對象的標記字段的鎖記錄(Lock Record)指向持有該鎖的線程的棧幀,這個過程不需要操作系統的介入,因此稱爲 “輕量級” 鎖。
-
• 自旋鎖:若鎖正在被另一個線程持有,則當前線程會進行自旋,即不斷循環檢查鎖是否可以被當前線程獲取。自旋可以避免線程在操作系統層面的掛起與恢復,但這會消耗 CPU 資源。
-
• 重量級鎖:如果自旋鎖執行後鎖還是無法獲得(比如持有鎖的線程長時間不釋放鎖或鎖競爭激烈的情況下),輕量級鎖便會升級爲重量級鎖,這時候會涉及到操作系統的 Mutex(互斥鎖)等機制。
-
• 鎖消除:編譯器優化階段,JVM 可以通過分析判斷,如果發現某段代碼中的鎖對象只被一個線程訪問,那麼 JVM 可以完全消除這個鎖。
-
• 偏向鎖:在 JVM 中,偏向鎖是 Java 6 之後引入的一種鎖優化方式。當鎖被一個線程佔用後,如果這個鎖沒有競爭,JVM 會把這個鎖偏向於第一個獲得它的線程,以後這個線程入鎖和解鎖將不需要再進行同步操作。若出現其他線程嘗試獲取這個鎖,偏向模式則會被取消,鎖會升級爲輕量級鎖。
示例演示
示例一:方法級別的同步
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + counter.getCount());
}
}
在這個示例中,Counter
類的 increment
、decrement
和 getCount
方法都被 synchronized
修飾,這意味着這些方法在同一時間只能被一個線程訪問。
當 thread1
和 thread2
分別調用 increment
和 decrement
方法時,它們會競爭對 count
變量的訪問。
由於 synchronized
關鍵字的存在,確保了每次只有一個線程可以執行這些方法,從而避免了多線程環境下的數據不一致問題。
示例二:代碼塊級別的同步
public class SafeCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public void decrement() {
synchronized (lock) {
count--;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
public static void main(String[] args) {
SafeCounter counter = new SafeCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + counter.getCount());
}
}
在這個示例中,increment
、decrement
和 getCount
方法內部使用了 synchronized
代碼塊,而不是修飾整個方法。
這裏使用了一個自定義的鎖對象 lock
來實現同步。
這種方式提供了更高的靈活性,因爲它允許在方法內部只對需要同步的代碼塊進行加鎖,而不是整個方法。
這有助於提高程序的併發性能。
示例三:靜態方法的同步
public class StaticCounter {
private static int count = 0;
public synchronized static void increment() {
count++;
}
public synchronized static void decrement() {
count--;
}
public synchronized static int getCount() {
return count;
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
decrement();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + getCount());
}
}
在這個示例中,StaticCounter
類的 increment
、decrement
和 getCount
方法都被聲明爲 static
並用 synchronized
修飾。
這意味着它們鎖定的是 StaticCounter
類的 Class
對象,而不是類的某個實例。當 thread1
和 thread2
分別調用這些靜態方法時,它們會競爭對 count
變量的訪問。
由於 synchronized
關鍵字的存在,確保了每次只有一個線程可以執行這些方法,從而避免了多線程環境下的數據不一致問題。
這些示例展示了 synchronized
關鍵字在不同場景下的應用,包括方法級別的同步、代碼塊級別的同步以及靜態方法的同步。
通過正確使用 synchronized
,可以在多線程編程中確保線程安全。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Mq_LGyXp0ulFh3LbNMIGGA