文章图片
Java中的volatile关键字特征和使用场景
1.不能保证原子性首先需要注意一点 , 那就是volatile无法保证原子性 。
一个很简单的实验可以证明这点:
public class Test { public volatile int inc = 0; public static void main(String[
args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for (int j = 0; j < 1000; j++) {
test.inc++;
;
.start();
//保证前面的线程都执行完 , eclipse是1 , IDEA是2 , 因为IDEA多了个监控线程
while (Thread.activeCount() > 2) {
Thread.yield();
System.out.println(Thread.activeCount());
System.out.println(test.inc);
按理说结果是10000 , 但是真正试了就会发现 , 没几次能到10000:
可以看到 , 不仅是有些误差 , 有时候甚至差了好几千 。
这就是因为volatile没有保证原子性的缘故 。
虽然它保证了可见性和有序性 , 让每个线程都能获取最新的变量 , 但是它不能保证同一时间只有一个线程能执行增加操作 。
前面说了inc++ , 这个操作并不具有原子性 。 自增操作 , 首先是读取了inc的值(从缓存中读取 , 并非从主存中读) , 然后进行加1运算 , 再将结果存回主存 , 这是三步操作 。 可见性的保证 , 是让他在步骤2之后能立即将值存回主存 , 并使其他线程中的该变量缓存失效 。
但是没有保证原子性的情况下 , 就会有这种可能性:
①线程1从自己的工作缓存中读取了inc的值 , 为0
②线程2从自己的工作缓存中读取了inc的值 , 为0 , 然后阻塞一会
③线程1对inc进行加一运算 , 得到结果1
④线程1将结果1存回主存 , 并将其他线程中的该变量的缓存设为失效
——到这里 , 线程1已经完成了他的一次操作 , 按理说不是设为失效了吗 , 为什么线程2还是会使用错误的值 。 因为线程2已经完成了【读取】这个操作了 , 只有在它读取前 , 将变量失效 , 它才能在读取时发现异常并去主存重新获取 。
⑤线程3从自己的工作缓存中读取inc的值 , 发现已经失效 , 重新从主存获取到了变量inc的最新值1 。
——这时候线程3能取到正确的值 , 因为它还没有进行读取这个操作 。
⑥线程2对inc进行加一运算 , 得到结果1
⑦线程2将结果1存回主存 , 并将其他线程中的该变量的缓存设为失效
问题就这样产生了 , 线程2自顾自的把错误的值设置了回去 , 线程1的操作相当于白费了 。
2.volatile的使用场景volatile的效率显然是要高于synchronized的 , 因为后者会阻止其他线程访问 , 相当于强制变成单线程模式 。 但是volatile却不能完全代替synchronized , 因为它不能保证原子性 。
有时候我们可以结合两者使用 , 这样可以缩减synchronized锁住的范围 。
- Java|事情闹大了!国内巨头官宣,一个也别想跑
- Java|大疆无人机拆解分析:一切都很好,但部分美国器件仍无法替代!
- javascript|微信支付功能被新疆法院冻结
- Java|120倍变焦+256GB,现已跌至“新低价”,“安卓之光”加速退场!
- |光电水位开关在无人机喷洒农药中的缺液提醒作用
- Java|移动办公优雅从容!华为MateBook E+悦滑键盘,简单又高能
- javascript|Web前端:2022年最好的JavaScript框架是什么?
- Java|2K价位闭眼入!OPPO K10首销拿下双冠军,除了游戏这些亮点也够强
- Java|华为手环7正式发布:全天候健康守护能力的大屏手环
- Java|移动推全国跨省宽带,电信联通跟不跟?