详解线程的三大特性:原子性、可见性和有序性
1. 原子性 (Atomicity)
定义 原子性指的是操作不可被中断,要么全部执行完成,要么完全不执行。
特性
-
原子性操作在执行时不会被其他线程干扰。
-
如果多个线程同时访问共享资源,原子性可以防止数据的不一致。
Java 中的原子性
-
原子性操作示例:
-
读取和写入基本数据类型(如
int
、float
)是原子性的。 -
对
volatile
变量的读取/写入是原子性的(不适用于复合操作)。
-
-
非原子性操作:
-
复合操作(如
counter++
或counter = counter + 1
)是非原子性的。这些操作实际上包括三步:读取变量值、修改值、写回变量。
-
解决方案
-
使用 同步机制(如
synchronized
块或方法):123synchronized
(lock) {
counter++;
}
-
使用 原子类(如
AtomicInteger
):12AtomicInteger counter =
new
AtomicInteger();
counter.incrementAndGet();
2. 可见性 (Visibility)
定义 可见性指的是一个线程对共享变量的修改对其他线程是可见的。
特性
-
在多线程环境中,如果没有同步机制,一个线程对变量的修改可能不会立刻被其他线程看到(由于 CPU 缓存或编译优化)。
-
线程可能会一直使用自己 CPU 缓存中的值,而看不到其他线程更新后的值。
Java 中的可见性
-
存在可见性问题的场景:
123456789101112131415161718private
static
boolean
flag =
false
;
public
static
void
main(String[] args) {
new
Thread(() -> {
while
(!flag) {
// do something
}
System.out.println(
"Thread ended."
);
}).start();
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
flag =
true
;
// 主线程修改了 flag,但子线程可能看不到
}
-
解决方案:
-
使用 volatile 关键字:
1private
static
volatile
boolean
flag =
false
;
-
volatile
确保变量的修改对所有线程立即可见。
-
-
使用 同步机制(如
synchronized
或显式锁Lock
),因为同步也能保证可见性。
-
3. 有序性 (Ordering)
定义 有序性指的是程序代码的执行顺序,通常来说,程序会按照代码编写的顺序执行,但编译器和处理器会为了优化性能进行 指令重排。
特性
-
在单线程环境中,指令重排不会影响程序的正确性。
-
在多线程环境中,指令重排可能导致意想不到的结果,因为线程之间的执行顺序无法预测。
指令重排的例子
1 2 3 | int a = 1 ; // (1) int b = 2 ; // (2) int c = a + b; // (3) |
在单线程中,执行顺序是 (1) -> (2) -> (3)
,但由于重排优化,CPU 可能将 (2)
和 (1)
的顺序交换。尽管结果在单线程环境中是正确的,但多线程中可能导致数据问题。
Java 中的有序性
-
可能出现问题的场景:
-
使用
volatile
可以禁止指令重排:1234567891011121314151617181920212223242526272829303132333435package
com.example.demopool;
import
org.springframework.stereotype.Component;
/**
* @Author: cv master
* @Date: 2024/11/15 09:17
*/
@Component
public
class
B {
private
boolean
flag =
false
;
private
int
a =
0
;
public
void
writer() {
a =
1
;
// 写变量
flag =
true
;
// 通知其他线程
}
public
void
reader() {
if
(flag) {
// 读标志
System.out.println(a);
// 此时 a 的值一定是 1
}
}
public
static
void
main(String[] args)
throws
InterruptedException {
B b =
new
B();
Thread thread1 =
new
Thread(b::writer);
Thread thread2 =
new
Thread(b::reader);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
-
在上述代码中,
volatile
保证了写操作的顺序,使得a = 1
一定发生在flag = true
之前。
-
解决方案
-
使用 volatile:确保关键变量的修改不会被指令重排。
-
使用 synchronized 或 显式锁:同步代码块可以强制线程按照指定顺序执行。
三者关系与 happens-before
原则
-
原子性 和 可见性 是独立的,但有时需要结合使用才能实现正确的多线程行为。
-
有序性 通常需要通过
volatile
或同步机制来确保。 -
happens-before
是 Java 内存模型中定义的一种原则,用于规定线程间的操作顺序:-
一个线程对变量的写操作对另一个线程的读操作可见,必须满足
happens-before
原则。 -
如:
synchronized
、volatile
、线程启动/终止等操作会建立happens-before
关系。
-
总结
特性 | 描述 | 解决方法 |
---|---|---|
原子性 | 操作不可中断,要么全部执行成功,要么完全不执行。 | 使用 synchronized 或原子类如 AtomicInteger 。 |
可见性 | 一个线程的修改对其他线程立刻可见。 | 使用 volatile 或同步机制如 synchronized 。 |
有序性 | 程序执行顺序符合预期,避免指令重排导致问题。 | 使用 volatile 、同步机制(synchronized 或锁)。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具