共享变量副本与 主内存共享变量的区别
# 一、核心区别
维度 | 主内存共享变量 | 共享变量副本(工作内存) |
---|---|---|
存储位置 | JVM 堆内存中的全局共享区域(如实例变量、静态变量) | 线程私有的本地内存(CPU 缓存、寄存器等硬件优化空间) |
数据可见性 | 所有线程可见,但需同步机制保证最新状态 | 线程私有,默认对其他线程不可见 |
同步机制 | 通过 volatile 、synchronized 或原子类强制刷新副本到主内存 | 默认无同步,线程直接操作副本,可能导致数据不一致 |
# 二、Java 代码示例与说明
# 1. 可见性问题:线程副本未同步导致死循环
public class VisibilityDemo {
private static boolean flag = false; // 主内存共享变量
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("Thread 1: Waiting...");
while (!flag) { // 操作工作内存中的副本
// 空循环
}
System.out.println("Thread 1: Detected flag change!");
}).start();
Thread.sleep(1000);
new Thread(() -> {
System.out.println("Thread 2: Changing flag...");
flag = true; // 修改副本,但未及时刷新到主内存
}).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输出结果:
Thread 1: Waiting...
Thread 2: Changing flag...
(线程1永远无法退出循环)
1
2
3
2
3
原因:线程2修改 flag
后未强制同步到主内存,线程1的工作内存副本仍为旧值 false
。
# 2. 使用 volatile
解决可见性问题
private static volatile boolean flag = false; // 强制读写直接操作主内存
1
输出结果:
Thread 1: Waiting...
Thread 2: Changing flag...
Thread 1: Detected flag change!
1
2
3
2
3
机制:volatile
通过内存屏障禁止指令重排序,并强制线程每次读写直接从主内存操作。
# 3. 使用 synchronized
保证可见性
public class SynchronizedDemo {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (SynchronizedDemo.class) {
System.out.println("Thread 1: Waiting...");
while (!flag) {
// 空循环
}
System.out.println("Thread 1: Detected flag change!");
}
}).start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (SynchronizedDemo.class) {
System.out.println("Thread 2: Changing flag...");
flag = true;
}
}).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输出结果:线程1正常退出循环。
机制:synchronized
在加锁时清空工作内存并强制从主内存读取最新值,解锁前将修改刷新到主内存。
# 4. 复合操作的原子性问题
public class AtomicityDemo {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 非原子操作(读取-修改-写入)
}
}).start();
}
Thread.sleep(2000);
System.out.println("Final count: " + count); // 结果可能小于 10000
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
原因:count++
是三个操作的组合(读取主内存值 → 修改副本 → 写回主内存),即使使用 volatile
也无法保证原子性。
解决方案:
• 使用 AtomicInteger
(基于 CAS 原子指令):
private static AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
1
2
2
• 使用 synchronized
块:
synchronized (AtomicityDemo.class) {
count++;
}
1
2
3
2
3
# 三、总结
主内存与副本的本质差异:
• 主内存是全局唯一真实值,副本是线程私有临时缓存。
• 线程默认操作副本,需同步机制保证主内存与副本的一致性。同步工具选择:
•volatile
:轻量级解决可见性和有序性,不保证原子性(如count++
)。
•synchronized
:保证可见性、原子性和有序性,但可能引发线程阻塞。
• 原子类(如AtomicInteger
):无锁实现原子操作,适合高并发场景。
通过合理使用同步机制,可避免因副本与主内存不一致导致的多线程问题。
编辑 (opens new window)
上次更新: 2025/06/13, 00:51:28