volatile 与synchronized
·
一、volatile 关键字
1. 核心作用
volatile 是轻量级的同步机制,仅能修饰成员变量(实例变量 / 类变量),核心解决两个问题:
- 可见性:一个线程修改了 volatile 变量的值,其他线程能立刻看到最新值(避免线程缓存导致的 “脏读”);
- 禁止指令重排序:保证变量的读写操作按代码顺序执行(比如单例模式的双重检查锁);
- ❗ 重要:不保证原子性(比如
count++这种复合操作依然会线程不安全)。
2. 基本用法
直接修饰变量,语法:volatile 数据类型 变量名;
3. 实用示例(状态标记位)
volatile 最典型的场景是 “线程开关 / 状态标记”,因为这类场景只需要可见性,不需要原子操作:
public class VolatileDemo {
// 用 volatile 修饰状态标记,保证多线程可见性
private volatile boolean isRunning = true;
// 工作线程
class WorkerThread extends Thread {
@Override
public void run() {
// 线程循环执行,直到 isRunning 变为 false
while (isRunning) {
// 模拟业务逻辑(空循环仅作演示)
}
System.out.println("工作线程:检测到状态变更,停止运行");
}
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
WorkerThread worker = demo.new WorkerThread();
worker.start();
// 主线程休眠2秒,让工作线程先运行
Thread.sleep(2000);
// 修改状态标记,工作线程能立刻感知
demo.isRunning = false;
System.out.println("主线程:已设置 isRunning = false");
// 等待工作线程结束
worker.join();
System.out.println("主线程:程序结束");
}
}
运行结果:
主线程:已设置 isRunning = false
工作线程:检测到状态变更,停止运行
主线程:程序结束
如果去掉 volatile,工作线程可能因为缓存了 isRunning 的值,永远无法感知到状态变更,导致无限循环。
4. 易错点:volatile 不保证原子性
下面的代码演示 volatile 无法解决复合操作的线程安全问题:
public class VolatileAtomicError {
private volatile int count = 0;
// 自增操作(count++ 是读-改-写复合操作)
public void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
VolatileAtomicError demo = new VolatileAtomicError();
// 启动1000个线程,每个线程执行1000次自增
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
}).start();
}
// 等待所有线程执行完毕(简单等待,实际开发用CountDownLatch更严谨)
Thread.sleep(2000);
// 预期结果是1000*1000=1000000,但实际结果远小于这个值
System.out.println("最终count值:" + demo.count);
}
}
原因:count++ 拆分为 3 步(读 count→加 1→写回),volatile 只能保证每一步的可见性,但无法保证这 3 步的原子性,多个线程会互相覆盖结果。
二、synchronized 关键字
1. 核心作用
synchronized 是重量级的同步锁,能解决原子性、可见性、有序性所有并发问题,本质是通过 “独占锁” 保证同一时刻只有一个线程执行同步代码。
2. 三种使用方式
| 使用方式 | 锁对象 | 适用场景 |
|---|---|---|
| 修饰实例方法 | 当前实例对象(this) |
保护实例变量的线程安全 |
| 修饰静态方法 | 当前类的 Class 对象 | 保护静态变量的线程安全 |
| 同步代码块 | 自定义锁对象(任意对象) | 细粒度锁,只同步关键代码 |
3. 实用示例
示例 1:修饰实例方法(解决计数器原子性问题)《每个实例各自一把锁》
public class SynchronizedMethodDemo {
private int count = 0;
// 同步实例方法:锁对象是 this
public synchronized void increment() {
count++; // 现在是原子操作
}
public static void main(String[] args) throws InterruptedException {
SynchronizedMethodDemo demo = new SynchronizedMethodDemo();
// 启动1000个线程,每个线程执行1000次自增
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
}).start();
}
Thread.sleep(2000);
// 结果稳定为1000000,解决了原子性问题
System.out.println("最终count值:" + demo.count);
}
}
示例 2:同步代码块(细粒度锁,性能更优)
如果整个方法只有一小段代码需要同步,用同步代码块更高效(减少锁竞争):
public class SynchronizedBlockDemo {
private int count = 0;
// 自定义锁对象(推荐用专门的Object作为锁,避免锁对象被意外修改)
private final Object lock = new Object();
public void increment() {
// 同步代码块:只锁定count++这一行,其他代码不受影响
synchronized (lock) {
count++;
}
}
// 其余代码和示例1一致,运行结果同样稳定为1000000
public static void main(String[] args) throws InterruptedException {
SynchronizedBlockDemo demo = new SynchronizedBlockDemo();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
}).start();
}
Thread.sleep(2000);
System.out.println("最终count值:" + demo.count);
}
}
示例 3:修饰静态方法(锁 Class 对象)《每个实例竞争同一把锁》
public class SynchronizedStaticDemo {
private static int staticCount = 0;
// 同步静态方法:锁对象是 SynchronizedStaticDemo.class
public static synchronized void staticIncrement() {
staticCount++;
}
public static void main(String[] args) throws InterruptedException {
// 启动1000个线程修改静态变量
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
SynchronizedStaticDemo.staticIncrement();
}
}).start();
}
Thread.sleep(2000);
System.out.println("最终staticCount值:" + SynchronizedStaticDemo.staticCount);
}
}
三、volatile vs synchronized 核心区别
| 特性 | volatile | synchronized |
|---|---|---|
| 修饰范围 | 仅能修饰成员变量 | 可修饰方法、同步代码块 |
| 原子性 | 不保证 | 保证 |
| 可见性 | 保证 | 保证 |
| 有序性 | 禁止指令重排序(保证) | 保证(通过锁实现) |
| 性能 | 轻量级,无阻塞 | 相对重量级,可能阻塞 |
| 适用场景 | 状态标记、单例 DCL | 复合操作(如 count++)、临界区代码 |
总结
volatile是轻量级同步,仅保证可见性和有序性,不保证原子性,适合简单的状态标记场景;synchronized是全能型同步锁,保证原子性、可见性、有序性,适合需要原子操作的临界区代码;- 实际开发中,优先用
volatile优化性能(能不用锁就不用),需要原子操作时再用synchronized(或更高效的Atomic原子类)。
更多推荐




所有评论(0)