Java中synchronized關鍵字怎么用 詳解Java同步鎖的4種使用方法

synchronized關鍵字在Java中用于實現線程同步,確保多線程并發訪問共享資源時的互斥執行。其主要使用方式包括:1. 同步代碼塊,通過指定對象作為鎖;2. 同步方法,鎖為當前對象(this)或類對象(class);3. 靜態同步方法,等價于使用類對象作為鎖;4. 同步靜態變量,通常使用靜態對象作為鎖。此外,synchronized依賴jvm的monitor機制,通過monitorenter和monitorexit指令實現鎖的獲取與釋放,并在jdk 1.6后通過鎖優化提升了性能。相比reentrantlock,synchronized是隱式鎖,自動釋放,而reentrantlock需要手動釋放且提供更多功能,但更易出錯。避免死鎖的方法包括統一鎖順序、限制鎖持有時間、使用定時鎖及工具檢測。

Java中synchronized關鍵字怎么用 詳解Java同步鎖的4種使用方法

synchronized關鍵字在Java中用于實現線程同步,確保多個線程在并發訪問共享資源時,只有一個線程可以執行特定代碼塊或方法,從而避免數據競爭和不一致性。它本質上提供了一種互斥鎖機制。

Java中synchronized關鍵字怎么用 詳解Java同步鎖的4種使用方法

解決方案

Java中synchronized關鍵字怎么用 詳解Java同步鎖的4種使用方法

synchronized關鍵字主要有四種使用方式:

立即學習Java免費學習筆記(深入)”;

Java中synchronized關鍵字怎么用 詳解Java同步鎖的4種使用方法

  1. 同步代碼塊(Synchronized Block):

    通過synchronized(Object)來指定一個對象作為鎖。任何時刻,只能有一個線程獲得該對象的鎖,其他線程必須等待。

    public class counter {     private int count = 0;     private Object lock = new Object(); // 鎖對象      public void increment() {         synchronized (lock) {             count++;         }     }      public int getCount() {         return count;     } }

    這里,lock對象充當了鎖,只有獲得lock鎖的線程才能執行count++操作。 選擇哪個對象作為鎖至關重要。 如果多個線程訪問不同的Counter實例,那么每個實例都有自己的lock對象,同步就無效了。

  2. 同步方法(Synchronized Method):

    將整個方法聲明為synchronized。 對于實例方法,鎖是當前對象(this);對于靜態方法,鎖是當前類(Class對象)。

    public class Counter {     private int count = 0;      public synchronized void increment() {         count++;     }      public int getCount() {         return count;     } }

    等價于:

    public class Counter {     private int count = 0;      public void increment() {         synchronized (this) {             count++;         }     }      public int getCount() {         return count;     } }

    靜態同步方法:

    public class Counter {     private static int count = 0;      public static synchronized void increment() {         count++;     }      public static int getCount() {         return count;     } }

    等價于:

    public class Counter {     private static int count = 0;      public static void increment() {         synchronized (Counter.class) {             count++;         }     }      public static int getCount() {         return count;     } }
  3. 同步靜態變量(不常用):

    雖然不常見,但也可以通過同步靜態變量來實現同步。 這本質上和同步代碼塊使用Class對象作為鎖是一樣的。

    public class Counter {     private static int count = 0;     private static Object staticLock = new Object();      public static void increment() {         synchronized (staticLock) {             count++;         }     }      public static int getCount() {         return count;     } }
  4. 隱式鎖(Intrinsic Lock or Monitor Lock):

    當線程進入synchronized塊或方法時,它會自動獲得與對象關聯的隱式鎖。 當線程退出synchronized塊或方法時,鎖會被自動釋放。 這種鎖是可重入的,意味著同一個線程可以多次獲得同一個鎖而不會阻塞。

Synchronized 和 ReentrantLock 的區別是什么?

  • synchronized 是 Java 關鍵字,屬于 JVM 層面,而 ReentrantLock 是一個類,屬于 API 層面。
  • synchronized 會自動釋放鎖,而 ReentrantLock 需要手動釋放鎖(lock.unlock()),如果忘記釋放,可能會導致死鎖。
  • ReentrantLock 提供了更多的功能,例如可中斷的鎖、公平鎖等,而 synchronized 相對簡單。
  • 性能方面,在 JDK 1.6 之后,synchronized 進行了大量的優化,在低并發情況下,性能可能優于 ReentrantLock,但在高并發情況下,ReentrantLock 通常性能更好。 理論上,ReentrantLock更靈活,但用不好也更容易出錯。

如何避免死鎖?

死鎖是指兩個或多個線程互相持有對方需要的資源,導致所有線程都無法繼續執行的情況。 避免死鎖的一些常見策略:

  • 避免循環等待: 確保線程獲取鎖的順序是一致的,避免形成循環依賴。
  • 限制鎖的持有時間: 盡量縮短持有鎖的時間,減少其他線程等待鎖的機會。
  • 使用定時鎖: 使用 ReentrantLock 的 tryLock(long timeout, TimeUnit unit) 方法,設置超時時間,避免無限等待。
  • 死鎖檢測: 一些工具可以檢測死鎖,例如 JConsole 和 VisualVM。

舉個例子,假設有兩個線程 A 和 B,它們都需要獲取鎖 lock1 和 lock2。如果線程 A 先獲取了 lock1,然后嘗試獲取 lock2,而線程 B 先獲取了 lock2,然后嘗試獲取 lock1,那么就可能發生死鎖。

Object lock1 = new Object(); Object lock2 = new Object();  // 線程 A new Thread(() -> {     synchronized (lock1) {         System.out.println("線程 A 獲取了 lock1");         try { Thread.sleep(100); } catch (InterruptedException e) {}         synchronized (lock2) {             System.out.println("線程 A 獲取了 lock2");         }     } }).start();  // 線程 B new Thread(() -> {     synchronized (lock2) {         System.out.println("線程 B 獲取了 lock2");         try { Thread.sleep(100); } catch (InterruptedException e) {}         synchronized (lock1) {             System.out.println("線程 B 獲取了 lock1");         }     } }).start();

在這個例子中,如果線程 A 獲取了 lock1,線程 B 獲取了 lock2,然后它們互相等待對方釋放鎖,就會發生死鎖。 避免這種情況,可以確保兩個線程以相同的順序獲取鎖,例如都先獲取 lock1,再獲取 lock2。

Synchronized的底層實現原理是什么?

synchronized 的實現依賴于 JVM 的 monitor(監視器鎖)機制。 每個 Java 對象都關聯一個 monitor,當線程執行到 synchronized 塊或方法時,會嘗試獲取 monitor 的所有權。 如果 monitor 沒有被其他線程持有,則該線程成功獲取 monitor,并將 monitor 的計數器加 1。 如果 monitor 已經被其他線程持有,則該線程會被阻塞,直到 monitor 的計數器變為 0,即持有 monitor 的線程釋放了鎖。

在 JVM 層面,synchronized 是通過 monitorenter 和 monitorexit 指令來實現的。 monitorenter 指令用于獲取 monitor 的所有權,monitorexit 指令用于釋放 monitor 的所有權。 JVM 會確保 monitorenter 和 monitorexit 指令是成對出現的,即使在發生異常的情況下,也會確保鎖被釋放,從而避免死鎖。 早期的synchronized效率比較低,因為是重量級鎖,會涉及到用戶態和內核態的切換,開銷較大。但是現在JVM對synchronized做了很多優化,比如鎖升級,輕量級鎖,偏向鎖等,在很多場景下synchronized的效率已經很高了。

? 版權聲明
THE END
喜歡就支持一下吧
點贊8 分享