目录
volatile有序性可见性是否能够保证原子性volatile
volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。
(资料图)
而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性。
那么什么是有序性,什么又是可见性呢?
有序性
那么什么是有序性呢?
其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序。
看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
但是在多线程
环境下,有些代码的顺序改变,有可能引发逻辑上的不正确。
而 volatile 就是因为有这个特性,所以才被大家熟知的。
volatile 又是如何保证有序性的呢?
有很多小伙伴就说,网上说的是 volatile 可以禁止指令指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到 volatile 修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行。
如果这时候,面试官不再继续深挖下去的话,那么恭喜你,可能这个问题已经回答完了,但是如果面试官继续往下深挖,为什么会禁止指令重排,什么又是指令重排呢?
在从源码到指令的执行,一般是分成了三种重排,如图所示:
我们接下来就得看看 volatile 是如何禁止指令重排的。
我们直接用代码来进行验证:
public class ReSortDemo { int a = 0; boolean flag = false; public void mehtod1(){ a = 1; flag = true; } public void method2(){ if(flag){ a = a +1; System.out.println("最后的值: "+a); } } }
如果有人看到这段代码,肯定会说,那这段代码出来的结果会是什么呢?
有些人说是 2,是的, 如果你只是单线程调用,那结果就是 2,但是如果是多线程调用的时候,最后的输出结果不一定是我们想象到的 2,这时就要把两个变量都设置为 volatile。
如果大家对单例模式了解比较多的话,肯定也是关注过这个 volatile,为什么呢?
大家看看如下代码:
class Singleton { // 不是一个原子性操作 //private static Singleton instance; //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生! private static volatile Singleton instance; // 构造器私有化 private Singleton() { } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
上面的单例模式大家熟悉么?
是的,这就是 **双重检查(DCL 懒汉式) **
有人会说,因为有指令重排序的存在,双端检索机制也也不一定是线程安全的呀,对呀,所以用到了 synchronized 关键字,让他变成了线程安全的了。
可见性
其实可见性就是,在多线程环境中,对共享变量的修改对于其他线程是否立即可见的问题。
那么他的可见性一般都会表现在什么地方呢?用在什么地方呢?
其实一般用这个变量,很多都是为了保证他的可见性,就比如定义的一个全局变量,在其中有个循环来判断这个变量的值,有一个线程修改了这个参数的时候,这个循环会停止,跳转到之后去执行。
我们来看看没有使用volatile修饰代码实现:
public class Test { private static boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); } }
结果大家肯定是可想而知,
运行结果肯定是:
线程A开始执行:
线程B开始执行
标识已经变更
确实,就是这样的。
如果我们用 volatile 呢,那么这个代码的执行结果就会不一样呢?
我们来试一下:
public class Test { private static volatile boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); }
这样我们就能看到另外一个执行结果,在循环当中的输出语句是可以被执行的。
也就是说,在线程B 中,我们去修改这个被修饰的变量,那么最终,在线程A中,就能顺利读取到我们的数据信息了。
是否能够保证原子性
不能,我们来看一点代码,被volatile修饰的变量,
public class Test { // volatile不保证原子性 // 原子性:保证数据一致性、完整性 volatile int number = 0; public void addPlusPlus() { number++; } public static void main(String[] args) { Test volatileAtomDemo = new Test(); for (int j = 0; j < 20; j++) { new Thread(() -> { for (int i = 0; i < 1000; i++) { volatileAtomDemo.addPlusPlus(); } }, String.valueOf(j)).start(); }// 后台默认两个线程:一个是main线程,一个是gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000 System.out.println(Thread.currentThread().getName() + " final number result = " + volatileAtomDemo.number); } }
如果能够保原子性,那么最终的结果应该是20000,但是每次的最终结果并不能保证就是20000,比如:
main final number result = 17114
main final number result = 20000
main final number result = 19317
三次执行,都是不同的结果,
为什么会出现这种呢?这就和number++有关系了
number++被拆分成3个指令
执行GETFIELD拿到主内存中的原始值number执行IADD进行加1操作执行PUTFIELD把工作内存中的值写回主内存中当多个线程并发执行PUTFIELD指令的时候,会出现写回主内存覆盖问题,所以才会导致最终结果不为 20000,所以 volatile 不能保证原子性。
到此这篇关于Java中Volatile关键字能保证原子性吗的文章就介绍到这了,更多相关Java Volatile关键内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
X 关闭
X 关闭
- 1联想拯救者Y70发布最新预告:售价2970元起 迄今最便宜的骁龙8+旗舰
- 2亚马逊开始大规模推广掌纹支付技术 顾客可使用“挥手付”结账
- 3现代和起亚上半年出口20万辆新能源汽车同比增长30.6%
- 4如何让居民5分钟使用到各种设施?沙特“线性城市”来了
- 5AMD实现连续8个季度的增长 季度营收首次突破60亿美元利润更是翻倍
- 6转转集团发布2022年二季度手机行情报告:二手市场“飘香”
- 7充电宝100Wh等于多少毫安?铁路旅客禁止、限制携带和托运物品目录
- 8好消息!京东与腾讯续签三年战略合作协议 加强技术创新与供应链服务
- 9名创优品拟通过香港IPO全球发售4100万股 全球发售所得款项有什么用处?
- 10亚马逊云科技成立量子网络中心致力解决量子计算领域的挑战