网站首页 > java教程 正文
获课:keyouit.xyz/6121/
一文搞懂 Java 线程安全:原子性、可见性、有序性的底层原理
Java 线程安全是并发编程的核心议题,理解其底层原理需要深入 Java 内存模型(JMM)。本文将结合 JMM 解析并发编程的三大特性(原子性、可见性、有序性)及其典型线程安全问题。
一、Java 内存模型(JMM)基础
JMM 定义了线程和主内存之间的抽象关系:
- 主内存(Main Memory):所有线程共享的内存区域
- 工作内存(Working Memory):每个线程私有的内存区域,存储主内存的副本
线程操作流程:
- 线程读取变量时,从主内存复制到工作内存
- 线程修改变量时,先在工作内存修改,再刷新回主内存
二、并发编程的三大特性
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 规则,用于保证程序的执行顺序:
- 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁
- volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读
- 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C
- start() 规则:如果线程 A 执行操作 ThreadB.start(),那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作
- join() 规则:如果线程 A 执行操作 ThreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回
- 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 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(); | |
} | |
} | |
} |
五、总结
- 原子性:操作不可分割,使用 synchronized、原子类等保证
- 可见性:一个线程的修改能被其他线程看到,使用 volatile、synchronized 等保证
- 有序性:程序执行顺序符合代码顺序,使用 volatile、synchronized 和 happens-before 原则保证
理解这些底层原理和解决方案,是编写高效、安全的并发程序的基础。在实际开发中,应根据场景选择合适的同步机制,平衡性能和安全性。
猜你喜欢
- 2025-07-19 Java AtomicInteger操作详解(java attachment)
- 2025-07-19 无锁同步-JAVA之Volatile、Atomic和CAS
- 2025-07-19 Java 作用域详解:从变量可见性到代码封装
- 2025-07-19 CompletableFuture源码分析(fragment源码分析)
- 2025-07-19 一文搞懂 CAS 操作与 ABA 问题:高并发编程的关键知识点
- 2025-07-19 Java 内存模型、JVM 内存结构(java的内存模型)
- 2025-07-19 Java面试必备八股文(java面试必备八股文河北人社)
- 2025-07-19 Java面试都在问的CAS,你还没掌握吗?
- 2025-07-19 java VarHandle介绍(java variant)
- 2025-07-19 【多线程】Java多线程与并发编程全解析
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)