Java中的volatile关键字

1 篇文章 0 订阅
1 篇文章 0 订阅

Java并发中的可见性与原子性

Java并发是一个十分重要的知识点,然而我并不会(…..),慢慢上手吧,今天来看一看这个volatile

可见性

可见性是指线程之间的可见性,也就是一个线程修改的结果对另一个线程是可见的。使用volatile修饰的变量就会具有可见性。但需要注意的是volatile只能保证被修饰的内容具有可见性,而不能保证具有原子性(单个volatile变量读写是原子性的),因而就会存在线程安全问题

原子性

原子是不可分割的,因此原子操作也是指某些操作是连续的不可分割的(操作系统中有详细的解释)。非原子操作会存在线程安全问题,而加上synchronized关键字后就会使操作变成原子操作

// ......
int a = 0;
a = a + 1;
// ......

这么一个简单的过程,CPU在运行的时候会先读取a的值,然后相加计算的结果会再赋值给a;这时候如果是多个线程在工作,那么在赋值操作前CPU读取的值到底是0还是1呢?(多个线程同时工作,无法得知哪个线程在CPU执行的先后顺序,此时使用的a值说不定就是彼时计算后的a值)

重排序-Java多线程

再看下面的内容之前,先要看一看这个重排序:指令重排序,是指编译器或程序运行时环境为了优化程序性能而采取的对指令重新排序执行的一种手段
简单的说,两条语句在执行时,处于优化的原因,谁先执行谁后不一定

synchronized和volatile

为了解决线程并发的问题,Java引入了同步快synchronized和volatile关键字机制

  • synchronized关键字:被synchronized修饰的块结构在多线程访问时,同一时刻只能有一个线程能有访问的到块内容
  • volatile关键字:volatile修饰的变量,线程在每次访问的时候,都会读取变量最后一次修改的值

注意

  • synchronized保证了原子性,但仍不代表线程安全
  • 如果一定要保证线程安全,可以使用重入锁ReentrantLock

volatile原理

再来仔细探讨一下volatile深入的原理,这是一种相对较弱的同步机制,能够确保使变量的更新对其他线程是可见的。被volatile声明的变量,编译器与运行时的环境都会注意到这是一个共享的变量,因此不会将该变量上的操作与其他内容操作一起重排序。这是因为volatile变量不会被缓存在寄存器或者其它对处理器不可见的地方,因此每次访问volatile变量都会返回最新更新的值。

处理器在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更为轻量级(稍弱)的同步机制

先看一下普通状态下的线程工作的内存变化

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
这里写图片描述
JVM在运行时会对不同的线程分配自己的线程栈(线程内存),线程栈保存了线程运行时变量的信息。当线程想访问一个对象的值的时候,会进行如下的操作:
- 首先,通过对象的引用找到对应在堆内存中变量的值
- 然后把该值load到本地线程内存中,建立一个变量的副本,之后线程就不在和对内存中变量的值有任何关系,而是直接修改副本中的值
- 在修改完副本变量值后,在线程完全退出前,会自动把线程副本变量的值写回到对象在堆中的值,这样堆中变量的值就发生了变化

如上图所示,取副本中的值(use)和写到副本中(asign)可以多次出现。重要的是,上图中的操作并不是原子性的,就是说当线程read和load后,如果主内存中变量的值发生了改变,线程无从得知,进而导致最后计算出的结果并不是我们预想中的。

回过头来看一下volatile的原理

还是上图,当使用volatile修饰后,JVM**只会**保证从主存加载到线程栈中的变量的值是最新的,这已经可以解释了volatile是如何使处理器总是使用到最新的变量值(依靠上图中蓝色的双向箭头)。

但是,注意但是,凡事都有个意外,volatile也会引发并发取值不一致的情况,原因在这里:
- 假设有一个线程1和一个线程2,两个线程都会取number变量的值,计算,并写回主存
- 先是线程1,read和load并计算写回后,number的值发生了变化
- 再是线程2,当线程2read和load时,可能会是线程1写回并更新之后的number的新值,当线程2计算并写回后,这个number的值还是我们想要的值嘛?

总结普通状态与加了volatile关键字的对比

简单的说,普通状态下,每个线程先从内存拷贝变量值到CPU缓存中(线程工作内存)。当有多个CPU工作时,每个线程可能在不同的CPU上被处理,也就是说,不同的线程使用的变量值都是来自不同的CPU缓存的

volatile生命的变量就保证了JVM每次读变量都从主存中读取,跳过了CPU缓存这一步

加了volatile关键词后带来的特性

  • 一就是可见性了,因为线程都是从主存读取数据,相当于线程利用主存传递数据
  • 二就是禁止了指令重排序,查看网上的博客,发现了这么一句指令代码load addl $0x0,(%esp),这是汇编指令,该操作相当于是一个内存屏障,作用是指令重排序时不能把屏障之后的指令排到屏障之前的位置

日常O_O

写这个Blog主要还是被笔试题虐了,关于JVM内存处理机制还是处于比较懵懂的状态,后面买了书再慢慢填坑
PS:今天心情爆炸不爽,服。自己还是先狗后人吧。:-)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值