concurrent 包的實現
由於 Java 的 CAS 同時具有 volatile 讀和 volatile 寫的內存語義,因此 Java 線程之間的通信現在有了下面四種方式:
A 線程寫 volatile 變量,隨後 B 線程讀這個 volatile 變量。
A 線程寫 volatile 變量,隨後 B 線程用 CAS 更新這個 volatile 變量。
A 線程用 CAS 更新一個 volatile 變量,隨後 B 線程用 CAS 更新這個 volatile 變量。
A 線程用 CAS 更新一個 volatile 變量,隨後 B 線程讀這個 volatile 變量。
同時,volatile 變量的讀/寫和 CAS 可以實現線程之間的通信。把這些特性整合在一起,就形成了整個 concurrent 包得以實現的基石。如果我們仔細分析 concurrent 包的源代碼實現,會發現一個通用化的實現模式:
首先,聲明共享變量爲 volatile;
然後,使用 CAS 的原子條件更新來實現線程之間的同步;
同時,配合以 volatile 的讀/寫和 CAS 所具有的 volatile 讀和寫的內存語義來實現線程之間的通信。
AQS,非阻塞數據結構和原子變量類(Java.util.concurrent.atomic 包中的類),這些 concurrent 包中的基礎類都是使用這種模式來實現的,而 concurrent 包中的高層類又是依賴於這些基礎類來實現的。從整體來看,concurrent 包的實現示意圖如下:
synchronized
synchronized 的要點
關鍵字 synchronized 可以保證在同一個時刻,只有一個線程可以執行某個方法或者某個代碼塊。
synchronized 有 3 種應用方式:
同步實例方法
同步靜態方法
同步代碼塊
同步實例方法
❌ 錯誤示例 - 未同步的示例
@NotThreadSafepublic class SynchronizedDemo01 implements Runnable { static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 100000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { SynchronizedDemo01 instance = new SynchronizedDemo01(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }// 輸出結果: 小於 200000 的隨機數字
Java 實例方法同步是同步在擁有該方法的對象上。這樣,每個實例其方法同步都同步在不同的對象上,即該方法所屬的實例。只有一個線程能夠在實例方法同步塊中運行。如果有多個實例存在,那麼一個線程一次可以在一個實例同步塊中執行操作。一個實例一個線程。
@ThreadSafepublic class SynchronizedDemo02 implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 100000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { SynchronizedDemo02 instance = new SynchronizedDemo02(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }// 輸出結果:// 2000000
同步靜態方法
靜態方法的同步是指同步在該方法所在的類對象上。因爲在 JVM 中一個類只能對應一個類對象,所以同時只允許一個線程執行同一個類中的靜態同步方法。
對於不同類中的靜態同步方法,一個線程可以執行每個類中的靜態同步方法而無需等待。不管類中的那個靜態同步方法被調用,一個類只能由一個線程同時執行。
@ThreadSafepublic class SynchronizedDemo03 implements Runnable { static int i = 0; public static synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 100000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new SynchronizedDemo03()); Thread t2 = new Thread(new SynchronizedDemo03()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }// 輸出結果:// 200000
同步代碼塊
有時你不需要同步整個方法,而是同步方法中的一部分。Java 可以對方法的一部分進行同步。
注意 Java 同步塊構造器用括號將對象括起來。在上例中,使用了 this
,即爲調用 add 方法的實例本身。在同步構造器中用括號括起來的對象叫做監視器對象。上述代碼使用監視器對象同步,同步實例方法使用調用方法本身的實例作爲監視器對象。
一次只有一個線程能夠在同步於同一個監視器對象的 Java 方法內執行。
@ThreadSafepublic class SynchronizedDemo04 implements Runnable { static int i = 0; static SynchronizedDemo04 instance = new SynchronizedDemo04(); @Override public void run() { synchronized (instance) { for (int j = 0; j < 100000; j++) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }// 輸出結果:// 200000
synchronized 的原理
synchronized 實現同步的基礎是:Java 中的每一個對象都可以作爲鎖。
對於普通同步方法,鎖是當前實例對象。
對於靜態同步方法,鎖是當前類的 Class 對象。
對於同步方法塊,鎖是 Synchonized 括號裏配置的對象。