Java并發編程-理論基礎

Java并發編程-理論基礎

1、什么是進程?

進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。

2、什么是線程?

線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱為線程。

線程是獨立調度和分派的基本單位。線程可以為操作系統內核調度的內核線程,如Win32線程;由用戶進程自行調度的用戶線程,如Linux平臺的POSIX Thread;或者由內核與用戶進程,如Windows 7的線程,進行混合調度。

同一進程中的多條線程將共享該進程中的全部系統資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調用棧(call stack),自己的寄存器環境(register context),自己的線程本地存儲(thread-local storage)。

一個進程可以有很多線程,每條線程并行執行不同的任務。

在多核或多CPU,或支持Hyper-threading的CPU上使用多線程程序設計的好處是顯而易見,即提高了程序的執行吞吐率。在單CPU單核的計算機上,使用多線程技術,也可以把進程中負責I/O處理、人機交互而常被阻塞的部分與密集計算的部分分開來執行,編寫專門的workhorse線程執行密集計算,從而提高了程序的執行效率。

進程與線程的對比:

特性

進程

線程

定義

資源分配的基本單位

CPU調度的基本單位

內存空間

獨立內存空間

共享進程內存空間

通信

需要IPC機制(管道、消息隊列等)

可直接讀寫進程數據段

創建開銷

大(需要分配獨立資源)

小(共享已有資源)

穩定性

一個進程崩潰不影響其他進程

一個線程崩潰可能導致整個進程崩潰

并發性

進程間并發

線程間并發

3、為什么需要多線程?

從CPU、內存、I/O速度差異的角度分析
計算機系統中,CPU、內存、I/O設備的速度差異極大:

  • CPU 計算速度極快(納秒級)
  • 內存 訪問速度較慢(百納秒級)
  • I/O設備(磁盤、網絡)更慢(毫秒級,比CPU慢百萬倍)

為了合理利用CPU的高性能,計算機體系結構、操作系統和編譯器分別采用了優化手段,但也引入了并發問題:

1. CPU緩存(Cache)——解決CPU與內存速度差異
  • 問題:CPU計算速度遠高于內存訪問速度,直接讀寫內存會導致CPU大部分時間等待(內存墻問題)。
  • 解決方案:引入多級緩存(L1/L2/L3 Cache),減少CPU訪問內存的次數。
  • 副作用(可見性問題)
    • 多線程環境下,一個線程修改了緩存數據,另一個線程可能無法立即看到(緩存一致性問題)。
    • 需要內存屏障(Memory Barrier)volatile關鍵字保證可見性。

總結:緩存提高CPU利用率,但導致多線程間數據不可見

2. 進程/線程分時復用——解決CPU與I/O速度差異
  • 問題:I/O操作(磁盤、網絡)極慢,單線程程序會讓CPU大部分時間等待I/O。
  • 解決方案
    • 進程:操作系統提供隔離的執行環境,但切換開銷大。
    • 線程:輕量級執行單元,共享進程資源,切換成本低。
    • 多線程:當一個線程等待I/O時,CPU可以執行其他線程(提高利用率)。
  • 副作用(原子性問題)
    • 線程切換可能導致非原子操作被中斷(如i++并非原子操作)。
    • 需要鎖(synchronized)CAS(Compare-And-Swap)保證原子性。

總結:多線程提高CPU利用率,但導致競態條件(Race Condition)

3. 編譯器/CPU指令重排序——優化緩存利用率
  • 問題:為了減少CPU等待數據的時間,編譯器和CPU會優化指令執行順序(如亂序執行)。
  • 解決方案
    • 指令重排:調整無關指令的順序,提高緩存命中率。
  • 副作用(有序性問題)
    • 多線程環境下,指令重排可能導致邏輯錯誤(如單例模式的雙重檢查鎖失效)。
    • 需要內存屏障(Memory Barrier)volatile禁止重排序。

總結:指令重排提高性能,但導致多線程執行順序不可預測

4、線程不安全如何產生的?(Java中)

如果多個線程對同一個共享數據進行訪問而不采取同步操作的話,那么操作的結果是不一致的。

銀行賬戶取款問題

public class UnsafeBankAccount {private int balance; // 共享數據public UnsafeBankAccount(int initialBalance) {this.balance = initialBalance;}// 不安全的取款方法public void withdraw(int amount) {if (amount <= balance) {// 模擬一些處理時間try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}balance -= amount;System.out.println(Thread.currentThread().getName() + " 取款 " + amount + " 成功,余額: " + balance);} else {System.out.println(Thread.currentThread().getName() + " 取款 " + amount + " 失敗,余額不足");}}public static void main(String[] args) {UnsafeBankAccount account = new UnsafeBankAccount(1000);// 創建多個線程同時取款for (int i = 0; i < 10; i++) {new Thread(() -> account.withdraw(800)).start();}}
}

運行結果:

問題分析
  1. 競態條件(Race Condition)
    • 多個線程同時檢查余額 if (amount <= balance)
    • 在檢查后但實際扣款前,其他線程可能已經修改了余額
    • 導致多個線程都認為可以取款,最終余額變為負數
  1. 操作非原子性

balance -= amount 不是原子操作,它實際上是:

    • java復制下載
int temp = balance;
temp = temp - amount;
balance = temp;
    • 線程可能在中間步驟被中斷
  1. 內存可見性問題
    • 一個線程對balance的修改可能不會立即對其他線程可見
    • 導致線程看到的是過期的balance值
線程不安全的核心原因
  • 共享數據的并發訪問:多個線程同時讀寫同一個變量
  • 操作的非原子性:操作不是一次性完成的,可能被中斷
  • 缺乏同步機制:沒有使用synchronized、Lock等同步手段
  • 內存可見性問題:線程可能看不到其他線程的最新修改

5、并發三要素

在并發編程中,問題的根源可以歸結為三個核心要素:可見性原子性、有序性

可見性(visibility)

可見性問題指的是一個線程對共享變量的修改,另一個線程不能立即看到。這是由于現代計算機架構中CPU緩存與主內存之間的同步延遲導致的。

可見性問題源于現代計算機的多級存儲架構

  1. CPU緩存架構:每個CPU核心有自己的緩存(L1、L2),多個核心共享L3緩存
  2. 緩存一致性協議:如MESI協議,但存在延遲
  3. 編譯器優化:可能將變量緩存在寄存器中而不是從內存讀取

在Java內存模型(JMM)中,每個線程有自己的工作內存(可以理解為CPU緩存的抽象),導致線程對共享變量的修改可能不會立即同步到主內存,其他線程也就無法立即看到修改。

可見性完整案例
案例1:體現可見性問題的服務控制器(基礎可見性問題)
package com.taiyuan.javademoone.threaddemo.demo001;import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 修改后的ServiceController,強制體現可見性問題* 通過純空循環和長時間運行來暴露可見性問題*/
public class ServiceController {// 無volatile修飾,保證會出現可見性問題private static boolean isRunning = true;//TODO 解決方案1、添加volatile修飾,保證可見性//    private static volatile boolean isRunning = true;// TODO 解決方案2、AtomicBoolean,保證可見性//    private static AtomicBoolean isRunning = new AtomicBoolean(true);// TODO 解決方案3、使用synchronized關鍵字,保證可見性//    public static synchronized boolean isRunning() {//        return isRunning;//    }//    public static synchronized void stopRunning() {//        isRunning = false;//    }// TODO 解決方案4、使用Lock,保證可見性private static final Lock lock = new ReentrantLock();public static boolean isRunning() {lock.lock();try {return isRunning;} finally {lock.unlock();}}public static void stopRunning() {lock.lock();try {isRunning = false;} finally {lock.unlock();}}// 添加一個計數器,用于驗證循環執行次數private static long counter = 0;public static void main(String[] args) throws InterruptedException {// 工作線程 - 使用純空循環Thread worker = new Thread(() -> {while (isRunning) {counter++;  // 純計數操作,無任何同步點}System.out.println("工作線程停止,循環次數: " + counter);});worker.start();// 主線程稍后停止服務Thread.sleep(3000);isRunning = false;//        stopRunning();System.out.println("主線程將isRunning設置為false");// 等待工作線程結束(最多10秒)worker.join(10000);if (worker.isAlive()) {System.out.println("警告:工作線程未能正常停止!");System.out.println("最后計數: " + counter);}}
}

解決可見性問題:

方案1:使用volatile(推薦)

private static volatile boolean isRunning = true;

方案2:使用AtomicBoolean

private static AtomicBoolean isRunning = new AtomicBoolean(true);// 在循環中
while (isRunning.get()) { ... }// 設置停止
isRunning.set(false);

方案3:使用synchronized方法

private static boolean isRunning = true;public static synchronized boolean isRunning() {return isRunning;
}public static synchronized void stopRunning() {isRunning = false;
}// 使用方式
while (isRunning()) { ... }
stopRunning();

方案4:使用Lock

private static boolean isRunning = true;
private static final Lock lock = new ReentrantLock();public static boolean isRunning() {lock.lock();try {return isRunning;} finally {lock.unlock();}
}public static void stopRunning() {lock.lock();try {isRunning = false;} finally {lock.unlock();}
}

原子性(Atomicity)

原子性是指一個操作或一系列操作作為一個不可分割的整體執行,要么全部完成,要么完全不執行。在多線程環境中,非原子操作會導致競態條件(Race Condition),產生不可預期的結果。

原子性三要素:

  1. 不可分割:操作過程不會被線程調度打斷
  2. 完全成功或完全失敗:沒有中間狀態
  3. 狀態一致性:操作前后系統狀態保持一致
原子性案例:
1、銀行賬戶轉賬(經典競態條件)
package com.taiyuan.javademoone.threaddemo.demo002;/*** BankAccount 類表示一個銀行賬戶,用于演示多線程環境下的轉賬操作*/
public class BankAccount {// 定義賬戶余額private int balance;// 構造函數,初始化賬戶余額public BankAccount(int initialBalance) {this.balance = initialBalance;}/** transfer 方法用于從一個賬戶向另一個賬戶轉賬*/public void transfer(BankAccount dest, int amount) {// 判斷賬戶余額是否足夠if (this.balance >= amount) {// 模擬處理延遲(放大競態窗口)try { Thread.sleep(10); } catch (InterruptedException e) {}// 扣款this.balance -= amount;// 收款dest.balance += amount;// 打印轉賬成功信息System.out.println(Thread.currentThread().getName() +" 轉賬成功: " + amount);}}/*** 獲取賬戶余額*/public int getBalance() {return balance;}
}
2、測試類(暴露問題)
package com.taiyuan.javademoone.threaddemo.demo002;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** 銀行轉賬示例*/
public class BankTransferDemo {public static void main(String[] args) throws InterruptedException {// 創建兩個賬戶,初始余額為1000BankAccount accountA = new BankAccount(1000);BankAccount accountB = new BankAccount(1000);// 輸出初始余額System.out.println("初始余額:");System.out.println("賬戶A: " + accountA.getBalance());System.out.println("賬戶B: " + accountB.getBalance());// 創建轉賬線程池ExecutorService executor = Executors.newFixedThreadPool(10);// 模擬100次從A向B轉賬10元for (int i = 0; i < 100; i++) {executor.execute(() -> accountA.transfer(accountB, 10));}// 模擬100次從B向A轉賬10元for (int i = 0; i < 100; i++) {executor.execute(() -> accountB.transfer(accountA, 10));}// 關閉線程池executor.shutdown();// 等待線程池中的任務執行完畢executor.awaitTermination(1, TimeUnit.MINUTES);// 輸出最終余額System.out.println("\n最終余額:");System.out.println("賬戶A: " + accountA.getBalance());System.out.println("賬戶B: " + accountB.getBalance());System.out.println("總額: " + (accountA.getBalance() + accountB.getBalance()));}
}

測試結果

結果分析:

出現總額不為2070的情況,說明發生了競態條件導致金額不一致。

3、解決方案

1、synchronized方法同步

package com.taiyuan.javademoone.threaddemo.demo003;/*** 線程安全的銀行賬戶類(使用synchronized解決原子性問題)*/
public class SynchronizedBankAccount {private int balance;public SynchronizedBankAccount(int initialBalance) {this.balance = initialBalance;}/*** 線程安全的轉賬方法** @param dest   目標賬戶* @param amount 轉賬金額* @return 轉賬成功返回true,否則返回false*/public boolean transfer(SynchronizedBankAccount dest, int amount) {// 按照賬戶對象的哈希值確定鎖順序,避免死鎖SynchronizedBankAccount first = this.hashCode() < dest.hashCode() ? this : dest;SynchronizedBankAccount second = this.hashCode() < dest.hashCode() ? dest : this;// 先鎖定賬戶1,再鎖定賬戶2  (避免死鎖)synchronized (first) {synchronized (second) {// 檢查余額是否充足if (this.balance < amount) {System.out.println(Thread.currentThread().getName() +" 轉賬失敗: 余額不足 (當前余額=" + this.balance + ")");return false;}// 模擬處理延遲try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}// 執行轉賬this.balance -= amount;dest.balance += amount;System.out.println(Thread.currentThread().getName() +" 轉賬成功: " + amount +" (轉出賬戶余額=" + this.balance +", 目標賬戶余額=" + dest.balance + ")");return true;}}}/*** 獲取賬戶余額(線程安全)*/public synchronized int getBalance() {return balance;}
}

測試:

package com.taiyuan.javademoone.threaddemo.demo003;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** 銀行賬戶轉賬測試類* 本類用于測試在多線程環境下,兩個銀行賬戶之間的轉賬操作是否能夠正確執行* 通過創建兩個初始余額相同的銀行賬戶,使用固定數量的線程執行多次相互轉賬操作,* 最后檢查兩個賬戶的總余額是否與初始總余額相等,以此驗證轉賬操作的線程安全性*/
public class BankTransferTest {// 初始余額常量private static final int INITIAL_BALANCE = 1000;// 每次轉賬金額常量private static final int TRANSFER_AMOUNT = 10;// 轉賬操作次數常量private static final int TRANSFER_COUNT = 100;// 線程池線程數量常量private static final int THREAD_COUNT = 10;public static void main(String[] args) throws InterruptedException {// 創建兩個銀行賬戶SynchronizedBankAccount accountA = new SynchronizedBankAccount(INITIAL_BALANCE);SynchronizedBankAccount accountB = new SynchronizedBankAccount(INITIAL_BALANCE);// 打印初始余額System.out.println("========== 初始余額 ==========");System.out.println("賬戶A余額: " + accountA.getBalance());System.out.println("賬戶B余額: " + accountB.getBalance());System.out.println("總額: " + (accountA.getBalance() + accountB.getBalance()));// 創建線程池ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);// 啟動轉賬任務System.out.println("\n========== 開始轉賬 ==========");for (int i = 0; i < TRANSFER_COUNT; i++) {// A向B轉賬executor.execute(() -> accountA.transfer(accountB, TRANSFER_AMOUNT));// B向A轉賬executor.execute(() -> accountB.transfer(accountA, TRANSFER_AMOUNT));}// 關閉線程池并等待任務完成executor.shutdown();executor.awaitTermination(1, TimeUnit.MINUTES);// 打印最終余額System.out.println("\n========== 最終余額 ==========");System.out.println("賬戶A余額: " + accountA.getBalance());System.out.println("賬戶B余額: " + accountB.getBalance());// 驗證總額不變int total = accountA.getBalance() + accountB.getBalance();int expectedTotal = 2 * INITIAL_BALANCE;System.out.println("總額: 實際=" + total + ", 預期=" + expectedTotal +(total == expectedTotal ? " (正確)" : " (錯誤!)"));}
}

運行結果:

2、ReentrantLock顯式鎖

package com.taiyuan.javademoone.threaddemo.demo004;import java.util.concurrent.locks.ReentrantLock;/*** BankAccount 類表示一個銀行賬戶,使用ReentrantLock保證線程安全*/
public class BankLockAccount {// 賬戶余額private int balance;// 可重入鎖private final ReentrantLock lock = new ReentrantLock();// 構造函數,初始化賬戶余額public BankLockAccount(int initialBalance) {this.balance = initialBalance;}/*** transfer 方法用于從一個賬戶向另一個賬戶轉賬* 使用ReentrantLock保證線程安全*/public void transfer(BankLockAccount dest, int amount) {// 確定鎖獲取順序(例如通過hashCode比較)這種通過對象哈希碼決定鎖順序的方法是一種常見的死鎖避免策略,稱為鎖排序(Lock Ordering)。BankLockAccount first = this.hashCode() < dest.hashCode() ? this : dest;BankLockAccount second = this.hashCode() < dest.hashCode() ? dest : this;first.lock.lock();try {second.lock.lock();try {if (this.balance >= amount) {try { Thread.sleep(10); } catch (InterruptedException e) {}this.balance -= amount;dest.balance += amount;System.out.println(Thread.currentThread().getName() +" 轉賬成功: " + amount);}} finally {second.lock.unlock();}} finally {first.lock.unlock();}}/*** 獲取賬戶余額*/public int getBalance() {lock.lock();try {return balance;} finally {lock.unlock();}}
}

測試:

package com.taiyuan.javademoone.threaddemo.demo004;import java.util.concurrent.locks.ReentrantLock;/*** BankAccount 類表示一個銀行賬戶,使用ReentrantLock保證線程安全*/
public class BankLockAccount {// 賬戶余額private int balance;// 可重入鎖private final ReentrantLock lock = new ReentrantLock();// 構造函數,初始化賬戶余額public BankLockAccount(int initialBalance) {this.balance = initialBalance;}/*** transfer 方法用于從一個賬戶向另一個賬戶轉賬* 使用ReentrantLock保證線程安全*/public void transfer(BankLockAccount dest, int amount) {// 確定鎖獲取順序(例如通過hashCode比較)這種通過對象哈希碼決定鎖順序的方法是一種常見的死鎖避免策略,稱為鎖排序(Lock Ordering)。BankLockAccount first = this.hashCode() < dest.hashCode() ? this : dest;BankLockAccount second = this.hashCode() < dest.hashCode() ? dest : this;first.lock.lock();try {second.lock.lock();try {if (this.balance >= amount) {try { Thread.sleep(10); } catch (InterruptedException e) {}this.balance -= amount;dest.balance += amount;System.out.println(Thread.currentThread().getName() +" 轉賬成功: " + amount);}} finally {second.lock.unlock();}} finally {first.lock.unlock();}}/*** 獲取賬戶余額*/public int getBalance() {lock.lock();try {return balance;} finally {lock.unlock();}}
}

3、使用AtomicReference(無鎖方案)

package com.taiyuan.javademoone.threaddemo.demo0005;import java.util.concurrent.atomic.AtomicReference;/*** 使用AtomicReference實現的無鎖銀行賬戶*/
public class AtomicBankAccount {// 使用AtomicReference包裝賬戶余額// AtomicReference<Integer>:提供對 Integer 類型值的原子操作,確保多線程環境下數據一致性。/*** AtomicReference 是 Java 中 java.util.concurrent.atomic 包的一部分,* 它提供了一種原子操作方式,用于更新引用類型的變量。* 與其他原子類(如 AtomicInteger 和 AtomicLong)類似,* AtomicReference 允許你在多線程環境下安全地對對象引用進行更新,* 而無需使用傳統的同步機制(例如 synchronized 關鍵字)。*/private final AtomicReference<Integer> balanceRef;public AtomicBankAccount(int initialBalance) {this.balanceRef = new AtomicReference<>(initialBalance);}/*** 無鎖轉賬實現** @param dest   目標賬戶* @param amount 轉賬金額* @return 轉賬成功返回true,否則返回false*/public boolean transfer(AtomicBankAccount dest, int amount) {// 參數檢查if (amount <= 0) {return false;}/** CAS 是一種 硬件支持的原子指令,它在 Java 中通過 AtomicReference 和 Unsafe 類等機制實現。它的核心思想是:* 在更新一個值時,先檢查該值是否與預期一致,如果一致則更新為新值,否則重試。*/// 使用CAS循環實現原子轉賬操作while (true) {// 獲取當前轉出賬戶余額的快照Integer current = balanceRef.get();// 檢查當前余額是否足夠進行轉賬if (current < amount) {// 余額不足時打印轉賬失敗信息,并返回false表示轉賬失敗System.out.println(Thread.currentThread().getName() +" 轉賬失敗: 余額不足");return false;}// 模擬處理轉賬過程中的延遲,增加并發情況下沖突的可能性try {Thread.sleep((int) (Math.random() * 10));} catch (InterruptedException e) {// 當線程被中斷時,設置中斷標志并返回false表示轉賬失敗Thread.currentThread().interrupt();return false;}// 使用CAS操作更新轉出賬戶的余額if (balanceRef.compareAndSet(current, current - amount)) {// 更新目標賬戶余額,同樣需要使用CAS操作以確保原子性while (true) {// 獲取目標賬戶當前余額的快照Integer destCurrent = dest.balanceRef.get();// 使用CAS操作更新目標賬戶余額,成功則打印轉賬信息并返回true表示轉賬成功if (dest.balanceRef.compareAndSet(destCurrent, destCurrent + amount)) {System.out.println(Thread.currentThread().getName() +" 轉賬成功: " + amount +" (轉出賬戶余額=" + (current - amount) +", 目標賬戶余額=" + (destCurrent + amount) + ")");return true;}}}// CAS操作失敗則重試整個轉賬過程}}public int getBalance() {return balanceRef.get();}
}

測試:

package com.taiyuan.javademoone.threaddemo.demo0005;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** AtomicBankTransferTest 類用于演示使用原子操作實現銀行賬戶轉賬的線程安全性*/
public class AtomicBankTransferTest {// 定義初始余額常量private static final int INITIAL_BALANCE = 1000;// 定義每次轉賬金額常量private static final int TRANSFER_AMOUNT = 10;// 定義轉賬次數常量private static final int TRANSFER_COUNT = 100;// 定義線程池大小常量private static final int THREAD_COUNT = 10;/*** 主函數執行多個線程安全的轉賬操作* @param args 命令行參數* @throws InterruptedException 如果在等待線程池終止時被中斷*/public static void main(String[] args) throws InterruptedException {// 初始化兩個賬戶,賬戶A和賬戶B,每個賬戶初始余額為INITIAL_BALANCEAtomicBankAccount accountA = new AtomicBankAccount(INITIAL_BALANCE);AtomicBankAccount accountB = new AtomicBankAccount(INITIAL_BALANCE);// 打印初始余額System.out.println("初始余額:");System.out.println("賬戶A: " + accountA.getBalance());System.out.println("賬戶B: " + accountB.getBalance());// 創建固定大小的線程池ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);// 啟動轉賬任務for (int i = 0; i < TRANSFER_COUNT; i++) {// 從賬戶A向賬戶B轉賬TRANSFER_AMOUNT金額executor.execute(() -> accountA.transfer(accountB, TRANSFER_AMOUNT));// 從賬戶B向賬戶A轉賬TRANSFER_AMOUNT金額executor.execute(() -> accountB.transfer(accountA, TRANSFER_AMOUNT));}// 關閉線程池,并等待所有任務完成executor.shutdown();// 等待所有任務完成executor.awaitTermination(1, TimeUnit.MINUTES);// 打印最終余額System.out.println("\n最終余額:");System.out.println("賬戶A: " + accountA.getBalance());System.out.println("賬戶B: " + accountB.getBalance());System.out.println("總額: " + (accountA.getBalance() + accountB.getBalance()));}
}

有序性(Ordering)

有序性(ordering):指程序執行的順序必須符合預期,不能出現亂序的情況。在多線程環境下,由于編譯器、處理器、緩存等因素的影響,程序執行的順序可能會出現不一致的情況,導致程序出現錯誤。為了保證有序性,可以使用volatile關鍵字或者顯式地使用鎖來實現。同時,Java提供了happens-before規則,它可以保證在特定情況下,操作的順序是按照預期的順序執行的。

導致有序性問題的三大原因:

  • 編譯器優化重排序:在不改變單線程語義的前提下,編譯器可能調整指令順序
  • 處理器指令級并行:現代CPU采用流水線、亂序執行等技術
  • 內存系統重排序:由于多級緩存的存在,內存操作可能表現出亂序行為

Java提供了多種機制來保證有序性:

1. volatile關鍵字

volatile關鍵字可以:

  • 禁止指令重排序(通過內存屏障實現)
  • 保證變量的可見性

2. synchronized關鍵字

synchronized通過互斥鎖保證:

  • 同一時刻只有一個線程能訪問同步代碼塊
  • 在進入同步塊前會清空工作內存,退出時會將變量刷新回主內存
  • 禁止指令重排序

3. final關鍵字

正確初始化的final字段可以保證:

  • 在構造函數完成初始化后對其他線程可見
  • 禁止對final字段的寫操作重排序到構造函數之外

4. happens-before原則

Java內存模型定義的happens-before關系保證了有序性,包括:

  • 程序順序規則
  • 監視器鎖規則
  • volatile變量規則
  • 線程啟動規則
  • 線程終止規則
  • 線程中斷規則
  • 對象終結規則
  • 傳遞性

5. 原子類

java.util.concurrent.atomic包下的原子類使用CAS操作和volatile語義保證有序性。

6、什么是happens-before原則?

Happens-Before原則是Java內存模型(JMM)的核心理論,它從語言層面定義了多線程環境中操作之間的可見性規則,為開發者提供了一種理解并發程序行為的框架。

Happens-Before關系本質上是一種可見性保證契約:

  • 如果操作A happens-before 操作B,那么A的所有寫操作對B都是可見的
  • 這種關系可以跨線程傳遞
  • JVM必須遵守這些規則,但可以在不違反規則的前提下進行優化

Happens-Before(先行發生)原則的定義:

  1. 程序次序規則(Program Order Rule):在一個線程內,按照控制流順序,書寫在前面的操作先行發生于書寫在后面的操作。
  2. 管程鎖定規則(Monitor Lock Rule):一個unlock操作先行發生于后面對同一個鎖的lock操作。
  3. volatile變量規則(Volatile Variable Rule):對一個volatile變量的寫操作先行發生于后面對這個變量的讀操作。
  4. 線程啟動規則(Thread Start Rule):Thread對象start()方法先行發生于此線程的每一個動作。
  5. 線程終止規則(Thread Termination Rule):線程中的所有操作都先行發生于對此線程的終止檢測,我們可以通過Thread.join()方法和Thread.isAlive()的返回值等手段檢測線程是否已經終止執行。
  6. 線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷發生。
  7. 對象終結規則(Finalizer Rule) :一個對象的初始化完成(構造函數結束)先行發生于它的finalize()方法的開始。
  8. 傳遞性(Transitivity):如果操作A先行發生于操作B,操作B先行發生于操作C,那就可以得出操作A先行發生于操作C的結論。

7、volatile、synchronized 和 final詳解

1. volatile關鍵字

特性:

  • 可見性:保證變量的修改對所有線程立即可見
  • 有序性:禁止指令重排序(通過內存屏障實現)
  • 不保證原子性:復合操作仍需同步

適用場景:

  • 單寫多讀的場景
  • 作為狀態標志位
  • 雙重檢查鎖定模式(DCL)
2. synchronized關鍵字

特性:

  • 互斥性:同一時刻只有一個線程能進入同步塊
  • 可見性:同步塊內的變量修改對其他線程可見
  • 原子性:保證代碼塊內的操作不可分割
  • 有序性:禁止指令重排序

使用方式:

  • 同步實例方法
  • 同步靜態方法
  • 同步代碼塊
3、final關鍵字

特性:

  • 不可變性:基本類型值不可變,引用類型引用不可變
  • 線程安全:正確構造的對象對所有線程可見
  • 初始化安全:禁止final字段的寫操作重排序到構造函數之外

使用場景:

  • 常量定義
  • 不可變對象
  • 安全發布對象

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/908674.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/908674.shtml
英文地址,請注明出處:http://en.pswp.cn/news/908674.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Tornado WebSocket實時聊天實例

在 Python 3 Tornado 中使用 WebSocket 非常直接。你需要創建一個繼承自 tornado.websocket.WebSocketHandler 的類&#xff0c;并實現它的幾個關鍵方法。 下面是一個簡單的示例&#xff0c;演示了如何創建一個 WebSocket 服務器&#xff0c;該服務器會接收客戶端發送的消息&a…

模塊化架構下的前端調試體系建設:WebDebugX 與多工具協同的工程實踐

隨著前端工程化的發展&#xff0c;越來越多的項目采用模塊化架構&#xff1a;單頁面應用&#xff08;SPA&#xff09;、微前端、組件化框架等。這類架構帶來了良好的可維護性和復用性&#xff0c;但也帶來了新的調試挑戰。 本文結合我們在多個模塊化項目中的真實經驗&#xff…

高考:如何合理選擇學科、專業以及職業

如何合理選擇學科、專業以及職業 一、自我認知&#xff1a;明確自身興趣與優勢&#xff08;一&#xff09;興趣探索&#xff08;二&#xff09;能力評估&#xff08;三&#xff09;價值觀與目標 二、外部調研&#xff1a;深入了解學科、專業與職業&#xff08;一&#xff09;學…

【新品解讀】一板多能,AXRF49 定義新一代 RFSoC FPGA 開發平臺

“硬件系統龐雜、調試周期長” “高頻模擬前端不穩定&#xff0c;影響采樣精度” “接收和發射鏈路難以同步&#xff0c;難以擴展更多通道” “數據流量大&#xff0c;處理與存儲跟不上” 這些是大部分客戶在構建多通道、高頻寬的射頻采樣鏈路時&#xff0c;面臨的主要問題。…

實現仿中國婚博會微信小程序

主要功能&#xff1a; 1、完成底部標簽導航設計、首頁海報輪播效果設計和宮格導航設計&#xff0c;如圖1所示 2、在首頁里&#xff0c;單擊全部分類宮格導航的時候&#xff0c;會進入到全部分類導航界面&#xff0c;把婚博會相關內容的導航集成到一個界面里&#xff0c;如圖2…

MySQL強化關鍵_020_SQL 優化

目 錄 一、order by 優化 1.未添加索引 2.添加索引 3.復合索引默認升序排列 4.復合索引降序排列 5.復合索引升序降序排列并用 6.總結 二、group by 優化 1.未添加索引 2.添加索引 3.添加復合索引 三、limit 優化 四、主鍵優化 1.主鍵設計原則 五、insert 優化…

湖北理元理律師事務所視角:企業債務優化的三維平衡之道

核心提示&#xff1a;債務優化的本質不是消滅債務&#xff0c;而是在法律框架內重建財務可持續性。 一、企業債務危機的典型誤區 某制造企業主曾向我坦言&#xff1a;“用新貸還舊貸3年&#xff0c;債務從200萬滾到500萬。”這類案例暴露出企業債務處置的共性痛點&#xff1a…

【Ragflow】27.RagflowPlus(v0.4.1):小版本迭代,問題修復與功能優化

概述 RagflowPlus v0.4.0 在發布后&#xff0c;收到了積極的反饋&#xff0c;同時也包含一些問題。 本次進行一輪小版本更新&#xff0c;發布 v0.4.1 版本&#xff0c;對已知問題進行修復&#xff0c;并對部分功能進行進一步優化。 開源地址&#xff1a;https://github.com/…

【hadoop】Flink安裝部署

一、單機模式 步驟&#xff1a; 1、使用XFTP將Flink安裝包flink-1.13.5-bin-scala_2.11.tgz發送到master機器的主目錄。 2、解壓安裝包&#xff1a; tar -zxvf ~/flink-1.13.5-bin-scala_2.11.tgz 3、修改文件夾的名字&#xff0c;將其改為flume&#xff0c;或者創建軟連接…

Linux 下 ChromeDriver 安裝

個人博客地址&#xff1a;Linux 下 ChromeDriver 安裝 | 一張假鈔的真實世界 Selenium 是一個用于 Web 應用程序測試的工具。可以通過它驅動瀏覽器執行特定的操作&#xff0c;如點擊、下滑、資源加載與渲染等。該工具在爬蟲開發中也非常有幫助。Selenium 需要通過瀏覽器驅動操…

Canal環境搭建并實現和ES數據同步

作者&#xff1a;田超凡 日期&#xff1a;2025年6月7日 Canal安裝&#xff0c;啟動端口11111、8082&#xff1a; 安裝canal-deployer服務端&#xff1a; https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…

STM32使用土壤濕度傳感器

1.1 介紹&#xff1a; 土壤濕度傳感器是一種傳感裝置&#xff0c;主要用于檢測土壤濕度的大小&#xff0c;并廣泛應用于汽車自動刮水系統、智能燈光系統和智能天窗系統等。傳感器采用優質FR-04雙料&#xff0c;大面積5.0 * 4.0厘米&#xff0c;鍍鎳處理面。 它具有抗氧化&…

鎖的藝術:深入淺出講解樂觀鎖與悲觀鎖

在多線程和分布式系統中&#xff0c;數據一致性是一個核心問題。鎖機制作為解決并發沖突的重要手段&#xff0c;被廣泛應用于各種場景。樂觀鎖和悲觀鎖是兩種常見的鎖策略&#xff0c;它們在設計理念、實現方式和適用場景上各有特點。本文將深入探討樂觀鎖和悲觀鎖的原理、實現…

Jinja2深度解析與應用指南

1. 概念與用途 1.1 核心概念 Jinja2是Python生態中功能強大的模板引擎&#xff0c;采用邏輯與表現分離的設計思想&#xff1a; 模板&#xff1a;包含靜態內容和動態占位符的文本文件&#xff08;.j2后綴&#xff09;渲染&#xff1a;將模板與數據結合生成最終文本的過程上下…

Ubuntu20.04中 Redis 的安裝和配置

Ubuntu20.04 中 Redis 的安裝和配置 Ubuntu 安裝 MySQL 及其配置 1. Redis 的安裝 更新系統包列表并安裝 Redis &#xff1a; # 更新包管理工具 sudo apt update# -y&#xff1a;自動確認所有提示&#xff08;非交互式安裝&#xff09; sudo apt install -y redis-server測…

Sklearn 機器學習 缺失值處理 填充數據列的缺失值

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 ??使用 Scikit-learn 處理數據缺失值的完整指南 在機器學習項目中,數據缺失是不可避…

Unity中如何播放視頻

1.創建一個原始圖像并調整布局平鋪整個畫布 2.創建自定義紋理并調整自定義紋理大小 3.添加視頻播放組件 4.將準備好的視頻素材拖入到視頻剪輯中 5.將自定義紋理拖入到目標紋理中 6.將自定義紋理拖入到原始圖像的紋理中 最后運行游戲&#xff0c;即可播放視頻 總結&#xff1a;

Spring通用類型轉換的實現原理

Spring通用類型轉換的實現原理 設計思路實現邏輯ConversionService&#xff1a;類型轉換服務入口ConverterRegister&#xff1a;轉換器注冊接口GenericConversionService1. Map<ConvertiblePair, GenericConverter> converters2. canConvert() 與 convert()&#xff1a;服…

紅黑樹完全指南:為何工程都用它?原理、實現、場景、誤區全解析

紅黑樹完全指南&#xff1a;為何工程都用它&#xff1f;原理、實現、場景、誤區全解析 作者&#xff1a;星之辰 標簽&#xff1a;#紅黑樹 #平衡二叉查找樹 #工程實踐 #數據結構 #面試寶典 引子&#xff1a;工程師的“性能焦慮”與樹的進化史 你以為樹只是算法題里的配角&#…

阿里云 RDS mysql 5.7 怎么 添加白名單 并鏈接數據庫

阿里云 RDS mysql 5.7 怎么 添加白名單 并鏈接數據庫 最近幫朋友 完成一些運維工作 &#xff0c;這里記錄一下。 文章目錄 阿里云 RDS mysql 5.7 怎么 添加白名單 并鏈接數據庫最近幫朋友 完成一些運維工作 &#xff0c;這里記錄一下。 阿里云 RDS MySQL 5.7 添加白名單1. 登錄…