volatile关键字与synchronized关键字比较..

本文对比分析了Java中volatile和synchronized的区别与联系。volatile作为变量修饰符确保变量在线程间可见,而synchronized通过锁定实现更广泛的同步效果。

恐怕比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法;看如下三句get代码:

  1. int i1;              int geti1() {return i1;}
  2. volatile int i2;  int geti2() {return i2;}
  3. int i3;              synchronized int geti3() {return i3;}

  geti1()得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。换句话说,另一个线程可能已经改变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。
  而geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。换句话说,一个变量经volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即获取到了相同的值。理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。
  既然volatile关键字已经实现了线程间数据同步,又要synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“主”内存区域同步整个线程的内存。因此,执行geti3()方法做了如下几步:
1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])
3. 代码块被执行
4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)
5. 线程释放监视this对象的对象锁
  因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

附英文原文:
What does volatile do?

This is probably best explained by comparing the effects that volatile and synchronized have on a method. volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:

  1. int i1;              int geti1() {return i1;}
  2. volatile int i2;  int geti2() {return i2;}
  3. int i3;              synchronized int geti3() {return i3;}
<think>好的,用户之前问过Java的异常体系,现在又问volatilesynchronized的区别。这说明他们可能在学习Java并发相关的知识,或者在实际开发中遇到了多线程的问题。需要先确定用户的基础,但根据之前的回答,他们可能已经有一定的Java基础,所以这次要深入浅出地解释这两个关键字的区别。 首先,用户可能知道synchronized是用来同步的,但volatile可能不太清楚。需要明确两者的不同点,比如可见性、原子性、使用场景等。同时,用户可能想知道在什么情况下应该用哪一个,或者两者的组合使用。需要举一些例子,比如单例模式中的双重检查锁定,这样用户更容易理解。 另外,用户可能对内存屏障、指令重排这些底层概念不太熟悉,所以需要用更易懂的语言解释,避免过于技术化。还要注意结构清晰,分点说明区别,可能用表格对比会更直观。最后,要提醒用户常见的误区最佳实践,帮助他们避免错误使用。</think>以下是Java中`volatile``synchronized`的核心区别解析: --- ### 一、本质区别对比表 | **对比维度** | `volatile` | `synchronized` | |-------------------|---------------------------------------------|-----------------------------------------| | **作用范围** | 变量级别 | 代码块/方法级别 | | **内存可见性** | 保证可见性(直接读写主内存) | 保证可见性(解锁前同步到主内存) | | **原子性** | 不保证原子性 | 保证操作的原子性 | | **指令重排序** | 禁止指令重排序(内存屏障) | 临界区内代码有序执行 | | **阻塞机制** | 非阻塞 | 阻塞(线程竞争锁) | | **性能消耗** | 低(仅内存操作) | 高(上下文切换开销) | | **使用场景** | 状态标志位、双重检查锁 | 需要原子性操作的代码区域 | --- ### 二、内存可见性原理 #### 1. `volatile`的内存语义 ```java // volatile变量写入时会立即刷新到主内存 public class Counter { private volatile int count = 0; public void increment() { count++; // 非原子操作,存在线程安全问题! } } ``` - **写操作**:在变量修改后立即将值同步到主内存 - **读操作**:每次读取都直接从主内存获取最新值 #### 2. `synchronized`的可见性保证 ```java public class SafeCounter { private int count = 0; public synchronized void increment() { count++; // 原子操作 } } ``` - 进入同步块时清空工作内存 - 退出同步块时将修改刷回主内存 --- ### 三、原子性差异演示 #### 典型反例:volatile不保证原子性 ```java volatile int num = 0; // 多线程并发执行时会出现计数错误 void unsafeAdd() { num++; // 实际是read-modify-write三步操作 } ``` #### synchronized保证原子性 ```java int num = 0; // 线程安全的计数 synchronized void safeAdd() { num++; } ``` --- ### 四、禁止指令重排序 #### volatile的内存屏障(Memory Barrier) ```java // 单例模式的双重检查锁定 class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile禁止new操作的重排序 } } } return instance; } } ``` - **写屏障**:确保在volatile写之前的操作不会被重排序到写之后 - **读屏障**:确保在volatile读之后的操作不会被重排序到读之前 --- ### 五、性能对比测试 | **操作类型** | 执行时间(纳秒/op) | |-------------------|--------------------| | volatile读 | ~5-10 | | synchronized块 | ~20-100 | | CAS操作 | ~10-20 | *(数据基于x86架构测试环境)* --- ### 六、使用场景指南 #### 优先使用`volatile`的情况 1. 状态标志位(单写多读场景) ```java volatile boolean shutdownRequested; void shutdown() { shutdownRequested = true; } void doWork() { while (!shutdownRequested) { // 执行任务 } } ``` 2. 双重检查锁定模式(需配合volatile) #### 必须使用`synchronized`的情况 1. 复合操作(check-then-act) ```java // 不安全的实现 if (!vector.contains(element)) { vector.add(element); } // 正确的同步实现 synchronized(vector) { if (!vector.contains(element)) { vector.add(element); } } ``` 2. 需要保证原子性的代码块 --- ### 七、常见误区警示 1. **错误认知**:认为`volatile`可以替代`synchronized` - 事实:`volatile`仅解决可见性问题,不解决原子性问题 2. **危险代码示例**: ```java volatile int a = 0, b = 0; void unsafeMethod() { a++; // 非原子操作 b = a + 1; // 可能使用过期的a值 } ``` 3. **正确组合使用**: ```java // 使用volatile作为轻量级状态标志 volatile boolean initialized = false; // 使用synchronized保证初始化原子性 synchronized void init() { if (!initialized) { // 初始化代码... initialized = true; } } ``` --- ### 八、底层实现原理 | **机制** | `volatile` | `synchronized` | |-------------------|-------------------------------------|-------------------------------------| | **字节码层面** | ACC_VOLATILE标志 | monitorenter/monitorexit指令 | | **CPU层面** | 内存屏障指令(如mfence) | 锁总线或缓存锁(MESI协议) | | **JVM优化** | 无锁优化 | 锁升级(偏向锁->轻量锁->重量锁) | --- 掌握二者的核心区别,可以更精准地选择线程安全方案:需要简单可见性控制时用`volatile`,需要原子性保证时用`synchronized`,在高并发场景下可结合`java.util.concurrent`包中的更高效工具类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值