Java并发编程从入门到进阶 多场景实战

yumo6661周前 (07-27)技术文章7

获课:keyouit.xyz/6121/

一文搞懂 Java 线程安全:原子性、可见性、有序性的底层原理

Java 线程安全是并发编程的核心议题,理解其底层原理需要深入 Java 内存模型(JMM)。本文将结合 JMM 解析并发编程的三大特性(原子性、可见性、有序性)及其典型线程安全问题。

一、Java 内存模型(JMM)基础

JMM 定义了线程和主内存之间的抽象关系:

  • 主内存(Main Memory):所有线程共享的内存区域
  • 工作内存(Working Memory):每个线程私有的内存区域,存储主内存的副本

线程操作流程:

  1. 线程读取变量时,从主内存复制到工作内存
  2. 线程修改变量时,先在工作内存修改,再刷新回主内存

二、并发编程的三大特性

1. 原子性(Atomicity)

定义:操作不可分割,要么全部执行,要么全部不执行

JMM 体现

  • 基本数据类型(除 long/double 外)的读写是原子的
  • volatile 保证变量的原子性读写(仅限 32 位)
  • synchronized 块中的操作是原子的

典型问题:竞态条件(Race Condition)

java


// 非原子操作导致的竞态条件


public class Counter {


private int count = 0;




public void increment() {


count++; // 非原子操作(读-改-写)


}


}

解决方案

  • 使用 synchronized
  • 使用 AtomicInteger 等原子类

2. 可见性(Visibility)

定义:一个线程对共享变量的修改,能及时被其他线程看到

JMM 原因

  • 线程修改变量后,可能不会立即刷新到主内存
  • 其他线程可能读取到旧值(工作内存缓存)

典型问题

java


public class VisibilityProblem {


private static boolean flag = false;




public static void main(String[] args) {


new Thread(() -> {


while (!flag) { // 可能永远无法退出循环


// 业务逻辑


}


}).start();




try {


Thread.sleep(1000);


} catch (InterruptedException e) {


e.printStackTrace();


}




flag = true; // 修改可能不会立即被其他线程看到


}


}

解决方案

  • volatile 关键字
  • synchronized
  • final 关键字(初始化时保证可见性)

3. 有序性(Ordering)

定义:程序执行的顺序按照代码的先后顺序执行

JMM 原因

  • 编译器优化(指令重排)
  • 处理器优化(乱序执行)
  • 内存系统的重排序

典型问题

java


public class ReorderingProblem {


private static int x = 0, y = 0;


private static int a = 0, b = 0;




public static void main(String[] args) throws InterruptedException {


Thread one = new Thread(() -> {


a = 1;


x = b; // 可能读取到 b=0


});




Thread two = new Thread(() -> {


b = 1;


y = a; // 可能读取到 a=0


});




one.start();


two.start();


one.join();


two.join();




System.out.println("(" + x + "," + y + ")"); // 可能输出 (0,0)


}


}

解决方案

  • volatile 禁止指令重排
  • synchronized
  • happens-before 原则

三、happens-before 原则

JMM 定义了 8 条 happens-before 规则,用于保证程序的执行顺序:

  1. 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作
  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁
  3. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读
  4. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C
  5. start() 规则:如果线程 A 执行操作 ThreadB.start(),那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作
  6. join() 规则:如果线程 A 执行操作 ThreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回
  7. 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  8. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始

四、典型线程安全问题解决方案

1. 使用synchronized

java


public class SynchronizedCounter {


private int count = 0;




public synchronized void increment() {


count++; // 保证原子性、可见性和有序性


}




public synchronized int getCount() {


return count; // 保证可见性


}


}

2. 使用volatile

java


public class VolatileFlag {


private volatile boolean flag = false;




public void setFlag(boolean flag) {


this.flag = flag; // 保证可见性


}




public boolean getFlag() {


return flag; // 保证可见性


}


}

3. 使用原子类

java


import java.util.concurrent.atomic.AtomicInteger;




public class AtomicCounter {


private AtomicInteger count = new AtomicInteger(0);




public void increment() {


count.incrementAndGet(); // 保证原子性和可见性


}




public int getCount() {


return count.get(); // 保证可见性


}


}

4. 使用Lock接口

java


import java.util.concurrent.locks.Lock;


import java.util.concurrent.locks.ReentrantLock;




public class LockCounter {


private int count = 0;


private final Lock lock = new ReentrantLock();




public void increment() {


lock.lock();


try {


count++; // 保证原子性、可见性和有序性


} finally {


lock.unlock();


}


}




public int getCount() {


lock.lock();


try {


return count; // 保证可见性


} finally {


lock.unlock();


}


}


}

五、总结

  1. 原子性:操作不可分割,使用 synchronized、原子类等保证
  2. 可见性:一个线程的修改能被其他线程看到,使用 volatile、synchronized 等保证
  3. 有序性:程序执行顺序符合代码顺序,使用 volatile、synchronized 和 happens-before 原则保证

理解这些底层原理和解决方案,是编写高效、安全的并发程序的基础。在实际开发中,应根据场景选择合适的同步机制,平衡性能和安全性。

相关文章

阶段1:编程基础与Java入门(java编程入门资料)

第一部分:基础知识Java的历史和特点Java程序的结构Java开发环境搭建(安装JDK,配置环境变量)数据类型和变量运算符控制流程(if语句,循环语句)数组类和对象封装、继承、多态访问修饰符构造方法...

编程宝典!我的第1本Java编程书《Java从入门到精通》

本书专门为Java初学者和爱好者打造,旨在使读者学会、掌握和能够进行项目开发。当您认真系统学习本书之后,就可以骄傲地说——“我是一位真正的Java程序员了!”,即使目前您还是初学者。【电子版获取方式见...

北京Java编程入门培训怎么选择?寻找您的编程之路的正确指南!

“北京Java编程入门培训怎么选择?”是很多在北京想学习Java朋友的一大疑问,尤其是对于零基础的朋友来说,选择一家靠谱的Java编程入门机构可以快速帮你入门。今天我们就来揭秘这一奥秘!首先,根据自...

Java编程学习难不难 怎样才能快速入门Java

  Java编程学习难不难?怎样才能快速入门Java?对于想要加入IT行业的人来说,Java是一个不错的选择,不仅人才需求大,就业薪资也非常不错。许多人都非常看好Java发展前景,接下来千锋小编就给大...

Java 多线程编程入门:线程创建、生命周期与基本控制

获课:bcwit.top/14205/获取ZY↑↑方打开链接↑↑一、线程创建:三种核心方式及其本质继承 Thread 类本质:重写 run() 定义任务逻辑,通过 start() 触发线程执行。局限:...

大数据编程入门:Java网络编程(专业java大数据编程培训)

如果想要编写出一个可以运行在多个设备上的程序,应该怎么做呢?答案是网络编程,今天小编将为大家带来大数据编程入门:Java网络编程。一、网络编程概念网络编程是指编写在通过网络连接的多个设备(计算机)上运...