广告位联系
返回顶部
分享到

Java中Volatile关键字能保证原子性吗

java 来源:互联网 作者:佚名 发布时间:2022-08-28 20:46:23 人浏览
摘要

volatile volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。 而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可

volatile

volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。

而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性。

那么什么是有序性,什么又是可见性呢?

有序性

那么什么是有序性呢?

其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序。

看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

但是在??多线程??环境下,有些代码的顺序改变,有可能引发逻辑上的不正确。

而 volatile 就是因为有这个特性,所以才被大家熟知的。

volatile 又是如何保证有序性的呢?

有很多小伙伴就说,网上说的是 volatile 可以禁止指令指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到 volatile 修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行。

如果这时候,面试官不再继续深挖下去的话,那么恭喜你,可能这个问题已经回答完了,但是如果面试官继续往下深挖,为什么会禁止指令重排,什么又是指令重排呢?

在从源码到指令的执行,一般是分成了三种重排,如图所示:                             

我们接下来就得看看 volatile 是如何禁止指令重排的。

我们直接用代码来进行验证:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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,为什么呢?

大家看看如下代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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修饰代码实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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 呢,那么这个代码的执行结果就会不一样呢?

我们来试一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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修饰的变量,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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 不能保证原子性。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.51cto.com/u_15430445/5585061
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计