欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > Java并发编程-理论基础

Java并发编程-理论基础

2025/6/9 13:43:02 来源:https://blog.csdn.net/weixin_50701238/article/details/148517993  浏览:    关键词: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字段的写操作重排序到构造函数之外

使用场景:

  • 常量定义
  • 不可变对象
  • 安全发布对象

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词