锁专题(三)工作5年了,竟然不知道 volatile 关键字
“工作 5 年了 , 竟然不知道 volatile 关键字!”
听着刚面试完的架构师一顿吐槽 , 其他几个同事也都参与这次吐槽之中 。
都说国内的面试是“面试造航母 , 工作拧螺丝” , 有时候你就会因为一个问题被PASS 。
你工作几年了?知道 volatile 关键字吗?
今天就让我们一起来学习一下 volatile 关键字 , 做一个在可以面试中造航母的螺丝工!
文章插图
volatile+介绍
volatileJava语言规范第三版中对 volatile 的定义如下:
java编程语言允许线程访问共享变量 , 为了确保共享变量能被准确和一致的更新 , 线程应该确保通过排他锁单独获得这个变量 。
Java语言提供了 volatile , 在某些情况下比锁更加方便 。
如果一个字段被声明成 volatile , java线程内存模型确保所有线程看到这个变量的值是一致的 。
语义一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后 , 那么就具备了两层语义:
- 保证了不同线程对这个变量进行操作时的可见性 , 即一个线程修改了某个变量的值 , 这新值对其他线程来说是立即可见的 。
- 禁止进行指令重排序 。
- 注意
ps: 一个意思是变化可见 , 一个是永不变化 。 自然水火不容 。
问题引入
- Error.java
//线程1boolean stop = false;while(!stop){doSomething();} //线程2stop = true;
这段代码是很典型的一段代码 , 很多人在中断线程时可能都会采用这种标记办法 。问题分析但是事实上 , 这段代码会完全运行正确么?即一定会将线程中断么?
不一定 , 也许在大多数时候 , 这个代码能够把线程中断 , 但是也有可能会导致无法中断线程(虽然这个可能性很小 , 但是只要一旦发生这种情况就会造成死循环了) 。
下面解释一下这段代码为何有可能导致无法中断线程 。
在前面已经解释过 , 每个线程在运行过程中都有自己的工作内存 , 那么线程1在运行的时候 , 会将stop变量的值拷贝一份放在自己的工作内存当中 。
那么当线程 2 更改了 stop 变量的值之后 , 但是还没来得及写入主存当中 , 线程 2 转去做其他事情了 ,
那么线程 1 由于不知道线程 2 对 stop 变量的更改 , 因此还会一直循环下去 。
使用 volatile第一:使用 volatile 关键字会强制将修改的值立即写入主存;
第二:使用 volatile 关键字的话 , 当线程2进行修改时 , 会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话 , 就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量 stop 的缓存行无效 , 所以线程 1 再次读取变量 stop 的值时会去主存读取 。
那么在线程 2 修改 stop 值时(当然这里包括 2 个操作 , 修改线程 2 工作内存中的值 , 然后将修改后的值写入内存) ,会使得线程 1 的工作内存中缓存变量 stop 的缓存行无效 , 然后线程 1 读取时 ,发现自己的缓存行无效 , 它会等待缓存行对应的主存地址被更新之后 , 然后去对应的主存读取最新的值 。
那么线程 1 读取到的就是最新的正确的值 。
volatile 保证原子性吗从上面知道 volatile 关键字保证了操作的可见性 , 但是 volatile 能保证对变量的操作是原子性吗?
问题引入
public class VolatileAtomicTest {public volatile int inc = 0;public void increase() {inc++;}public static void main(String[] args) {final VolatileAtomicTest test = new VolatileAtomicTest();for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {test.increase();}}).start();}//保证前面的线程都执行完while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(test.inc);}}
- 计算结果是多少?
原因可能有的朋友就会有疑问 , 不对啊 , 上面是对变量 inc 进行自增操作 , 由于 volatile 保证了可见性 ,那么在每个线程中对inc自增完之后 , 在其他线程中都能看到修改后的值啊 , 所以有10个线程分别进行了 1000 次操作 , 那么最终inc的值应该是 1000*10=10000 。
这里面就有一个误区了 , volatile 关键字能保证可见性没有错 , 但是上面的程序错在没能保证原子性 。
可见性只能保证每次读取的是最新的值 , 但是 volatile 没办法保证对变量的操作的原子性 。
- 解决方式
- 采用|消息称一加9系列将推出三款新机,新增一加9E
- 同比|亚马逊公布“剁手节”创纪录战绩:第三方卖家全球销售额超48亿美元 同比大增60%
- 王文鉴|从工人到千亿掌门人,征服华为三星,只因他36年只坚持做一件事
- 俄罗斯手机市场|被三星、小米击败,华为手机在俄罗斯排名跌至第三!
- 操盘|中兴统一操盘中兴、努比亚、红魔三大品牌
- 责令|1336款APP被责令整改,三大问题突出
- 三个目标之后|品味莲乡 | 品味
- 苹果|iPhone13迎来变化!或回归指纹解锁,这几点备受用户喜爱
- 星期一|亚马逊:黑五与网络星期一期间 第三方卖家销售额达到48亿美元
- 短视频平台|大数据佐证,抖音带动三千万就业,视频手机将成生产力工具?