volatile详解
文章目录
- 1.什么是volatile?
- 2.可见性
- 3.volatile可见性代码证明
- 3.不保证原子性
- 4.禁止指令重排
- 5.volatile应用
1.什么是volatile?
volatile是java虚拟机提供的轻量级的同步机制,一共有三大特性,保证可见性,不保证原子性,禁止指令重排。
2.可见性
首先提一个JMM的概念,JMM是Java内存模型,它描述的是一组规范或规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。JMM规定所有的变量都在主内存,主内存是公共的所有线程都可以访问,线程对变量的操作必须是在自己的工作内存中。在这个过程中可能出现一个问题。
现在假设主物理内存中存在一个变量他的值为7,现在线程A和线程B要操作这个变量所以他们首先要将这个变量的值拷贝一份放到自己的工作内存中,如果A将这个值改为1,这时候线程B要使用这个变量但是B线程工作内存中的变量副本是7不是新修改的1这就会出现问题。所以JMM规定线程解锁前一定要将自己工作内存的变量写回主物理内存中,线程加锁前一定要读取主物理内存的值。也就是说一旦A修改了变量值,B马上能知道并且能更新自己工作内存的值。这就是可见性。
3.volatile可见性代码证明
volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改。我们用代码证明一下。
设计思路首先我们新建一个类里面有一个number,然后写一个方法可以让他的值变成60,这时候在主线程中开启一个新的线程让他3s后将number值改为60然后主线程写一个循环如果主线程能立刻监听到number值的改变则主线程输出改变后的值此时说明有可见性。如果一直死循环说明主线程没有监听到number值的更改说明不具有可见性。
class MyData{public int number = 0;public void change(){number = 60;}
}public class VolatileTest {public static void main(String[] args) {MyData myData = new MyData();new Thread(()->{System.out.println(Thread.currentThread().getName() + "number is :"+ myData.number);try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.change();System.out.println(Thread.currentThread().getName() + "has changed" + myData.number);},"A").start();while(myData.number == 0){}System.out.println(Thread.currentThread().getName() + "number is:" + myData.number);}
}
看一下结果
结果是进入了死循环一直空转,说明不具有可见性。下面我们在number前面加上关键字volatile。
证明能监控到number值已经修改说明加上volatile具有可见性。
3.不保证原子性
原子性指的是不可分割,完整性,也即某个线程正在做某个业务时不能被分割,要么同时成功,要么同时失败。为了证明volatile能不能保证原子性我们可以通过一个案例来证明一下。首先我们在之前的mydata类中加入一个方法addplus()能让number加1,然后我们创建20个线程然后每个线程调用1000次addplus()。看看结果如果number是20000那么他就能保证原子性如果不是20000那么就不能保证原子性。
class TestData{public static volatile int number = 0;public void change(){number = 60;}public void addPlus(){number++;}
}
public class VolatileDemo {public static void main(String[] args) {myData data = new myData();for (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {data.addPlus();}},String.valueOf(i)).start();}while(Thread.activeCount() > 2){Thread.yield();}System.out.println("number is :" + TestData.number);}
}
结果不是20000说明不能保证原子性。
不保证原子性的原因:number++这个操作一共有3步第一步从主物理内存中拿到number的值第二步number + 1,第三步写回主物理内存。
假设一开始主物理内存的值为0线程1,线程2分别读取主物理内存的值到自己的工作空间,然后执行加1操作。这时候按理说线程1将1写回主物理内存然后线程2读取主物理内存的值然后加1变成2,但是在1写回的过程中突然被打断线程1挂起,线程2将1写回主物理内存这时候线程1重新将1写回主物理内存最终主物理内存的值为1,两个线程加了两次最后值居然是1,出错了。
如何解决volatile的原子性问题呢?
我们需要使用原子类,原子类是保证原子性的。加入一个AtomicInteger类的数据然后调用他的getAndIncrement()方法(就是把这个书加1底层用CAS保证原子性)
class TestData{public static volatile int number = 0;public static AtomicInteger atomicInteger = new AtomicInteger();public void change(){number = 60;}public void addPlus(){number++;}public void AtomicAdd(){atomicInteger.getAndIncrement();}
}
public class VolatileDemo {public static void main(String[] args) {TestData data = new TestData();for (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {data.addPlus();data.AtomicAdd();}},String.valueOf(i)).start();}while(Thread.activeCount() > 2){Thread.yield();}System.out.println("number is :" + TestData.number);System.out.println("atomicInteger is:" + TestData.atomicInteger);}
}
结果是这个
这就解决了不保证原子性的问题。
4.禁止指令重排
计算机编译器在执行代码的时候不一定非得按照你写代码的顺序执行。他会经历编译器优化的重排,指令并行的重排,内存系统的重排,最终才会执行指令,多线程环境更是如此,可能每个线程代码的执行顺序都不一样,这就是指令重排。
public class VolatileReSort {int a = 0;boolean flag = false;public void method1(){//线程1a = 1;flag = true;}public void method2(){//线程2if(flag){a = a + 5;System.out.println("**********retValue:" + a);}}
}
假设现在线程1,线程2分别执行上面两种方法由于指令的重排序,可能线程1中的两条语句发生了指令重排,flag先变为true然后这是后线程2突然进来判断flag为true然后执行下面的最后输出结果为a = 5,但是也有可能先执行a = 1那这样结果就是a = 6所以由于指令重排可能导致结果不一定。现在加上volatile关键字他会在指令间插入一条Memory Barrier,来保证指令按照顺序执行不被重排。
5.volatile应用
传统的单例模式,在单线程下其实是没有什么问题的,多线程条件下就不行了。
public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName() +" :构造方法被执行");}public static SingletonDemo getInstance(){if(instance == null){instance = new SingletonDemo();}return instance;}public static void main(String[] args) {for(int i = 0;i<10;i++){new Thread(()->{SingletonDemo.getInstance();},String.valueOf(i)).start();}}
}
可以看出多线程下单例模式将会失效。我们通过DCL双重检查锁可以解决上述问题。
public static SingletonDemo getInstance(){if(instance == null){synchronized (SingletonDemo.class){if(instance == null){instance = new SingletonDemo();}}}return instance;}
但是这种方式也有一定的风险。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。instance = new SingletonDemo();可以分为一下3步完成1.分配对象内存空间2.初始化对象3.设置instance指向刚分配的内存地址,此时instance!=null。但是由于编译器优化可能会对2,3两步进行指令重排也就是先设置instance指向刚分配的内存地址但是这时候对象还没有初始化,如果这时候新来的线程调用了这个方法就会发现instance!=null然后就返回instance实际上instance没被初始化,也就造成了线程安全的问题。为了避免这个问题我们可以使用volatile对其进行优化禁止他的之指令重排就不会发生上述问题了。
private static volatile SingletonDemo instance = null;
volatile详解相关推荐
- C语言:关键字volatile详解!
一.volatile 介绍 参看:volatile详解 参看:C Language Keywords Indicates that a variable can be changed by a bac ...
- Java多线程之volatile详解
Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...
- Java基础:volatile详解
Java基础:volatile详解 1.volatile保证可见性 1.1.什么是JMM模型? 1.2.volatile保证可见性的代码验证 1.2.1.无可见性代码验证 1.2.1.volatile ...
- java内存 海子_Java虚拟机:JVM内存模型和volatile详解
JVM内存模型和volatile详解 Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内 ...
- Java Volatile 详解
Java Volatile 详解 Volatile:是java虚拟机提供的轻量级的同步机制.保证可见性.禁止指令重排序.不保证原子性!!! 学习Volatile之前必须了解JAVA内存模型. Java ...
- java volatile详解,互联网 面试官 如何面试
写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家.扫码加微信好友进[程序员面试学习交流群],免费领取.也欢迎各位一起在群里探讨技术. 1. ...
- java并发编程之Volatile详解
前言 在Java中多个线程对公共变量的操作并不是直接在内存中操作的,每一个线程都会有一块自己的工作内存.线程会先从主内存中获取到变量的值到工作内存中进行修改在更新到主内存.假如有两个线程同时对某个变量 ...
- volatile详解(任何人都能懂的那种)
volatile 看了好多篇博客终于明白这个关键字到底是干嘛的-让我综合所有的博客写一篇大家都能理解它的博客,要点赞呦!!! volatile是一个类型修饰符,作用是作为指令关键字,一般都是和cons ...
- Volatile详解,太详细了
Volatile可能是面试里面必问的一个话题吧,对他的认知很多朋友也仅限于会用阶段,今天我们换个角度去看看. 先来跟着丙丙来看一段demo的代码: 你会发现,永远都不会输出有点东西这一段代码,按道理线 ...
- Java进阶:AtomicReference详解
前言 Atomic家族主要是保证多线程环境下的原子性,相比synchronized而言更加轻量级.比较常用的是AtomicInteger,作用是对Integer类型操作的封装,而AtomicRefer ...
最新文章
- 阿里达摩院刷新纪录,开放域问答成绩比肩人类水平,超微软、Facebook
- 自动驾驶中可解释AI综述和未来研究方向
- Python 调用matplotlib模块绘制柱状图
- 《C语言程序设计与实践(第2版)》——3.2 数据类型
- [转]建一个XMLHttpRequest对象池
- exe注册为service服务
- 什么是Google On.Here,以及如何设置?
- PingCAP创始人刘奇:TiDB设计理念进化与大规模实践
- 市民举报邻居去韩国代购归来未隔离,真相很尴尬:表面防疫,实则打假?
- Unity3d AR 增强现实技术列表(2016年3月31日更新)
- 异常关闭MyEclipse 8.6后,不能重启
- 实现左侧固定宽度, 右侧自适应的两栏布局常见方法
- 下载篇:程序员修炼之道+从小工到专家(高清、免费)
- 2021年美国大学生数学建模竞赛C题参考翻译
- 【观察】 通盘无妙手,看SmartX如何构建增长根基
- 在Windows下安装Vim编辑器
- 写了一个简单的画板 箭头比较难搞 虚线 虚直线 实线 实直线 椭圆 圆 正方形
- window系统cmd的常用几条指令
- 优秀课件笔记之计算机软件立法保护
- 读《徐家骏:我在华为工作十年的感悟》有感