Java的Volatile实例用法及讲解

所属分类: 软件编程 / java 阅读数: 55
收藏 0 赞 0 分享

在原子性、可见性、有序性中,volatile关键字主要在可见性中发挥作用。

volatile声明的变量对所有线程来说是可见的,就是说当变量的值发生改变的时候,其他线程可以立马发现这个变化。

public class Main {
  private static boolean isRuning;
  private static int number;

  private static class ReaderThread extends Thread {
    public void run() {
      while (!isRuning) {
        System.out.println(number);
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    new ReaderThread().start();
    Thread.sleep(1000);
    number = 42;
    isRuning = true;
    Thread.sleep(1000);
  }
}

应该是由于编译器优化的存在,这里变量虽然没有被volatile修饰,但是仍然对其他线程可见。。。。。

那为啥Volatile修饰的变量i++却会有并发问题呢?

因为i++并不是原子操作,

i++是有两步操作的,比如 i=0; i++

1.读取i=0

2.计算i+1,然后赋值给i

那么可能存在2个线程同时读取到i=0,并计算出结果i=1然后赋值给I

那么就得不到预期结果i=2。

就是说虽然Volatile修饰的变量的变化可以被其他线程看到,但是如果同时去读这个变量,然后进行写操作,则仍会导致线程安全问题。

更底层的原因是什么呢?

首先要知道Volatile修饰的变量会做两件事(由lock指令完成):

  • 1)将当前处理器缓存行的数据写回到系统内存。
  • 2)写回内存的操作会使在其他 CPU 里缓存了该内存地址的额数据无效。

其他缓存会失效,不正好可以保证Volatile的原子性吗?

然而并不是,

比如有T1 T2两个线程进行i++操作。

当T1将变量加载到缓存,但是还没进行i++运算,T2呢已经加载完缓存并且已经执行完运算,那这个时候T1缓存里的值就该变成无效的了。

但是Volatile并不是让其他线程缓存无效以后就去重新加载主内存中的值,如果这时候T2缓存的值已经被放到寄存器并且cpu进行计算了,那即使缓存无效也不会影响T2将计算的值回写到主内存中。

关于cpu执行指令的过程可以参考https://blog.csdn.net/jizhu4873/article/details/84393905

当一个变量定义为 volatile 之后,将具备两种特性:

1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现。

内存屏障则由lock指令实现

以上就是本次介绍的全部知识点内容,感谢大家对脚本之家的支持。

更多精彩内容其他人还在看

Java基于反射机制实现全部注解获取的方法示例

这篇文章主要介绍了Java基于反射机制实现全部注解获取的方法,结合实例形式分析了java反射机制获取注解的具体实现方法与操作注意事项,需要的朋友可以参考下
收藏 0 赞 0 分享

Java 信号量Semaphore的实现

这篇文章主要介绍了Java 信号量Semaphore的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

eclipse+maven+spring mvc项目基本搭建过程

这篇文章主要介绍了eclipse+maven+spring mvc项目基本搭建过程,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Spring boot集成swagger2生成接口文档的全过程

这篇文章主要给大家介绍了关于Spring boot集成swagger2生成接口文档的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
收藏 0 赞 0 分享

Java冒泡排序法和选择排序法的实现

这篇文章主要介绍了Java冒泡排序法和选择排序法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Spring Cloud Alibaba教程之Sentinel的使用

这篇文章主要介绍了Spring Cloud Alibaba教程之Sentinel的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Josephus环的四种解法(约瑟夫环)基于java详解

这篇文章主要介绍了Josephus环的四种解法(约瑟夫环)基于java详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Java继承Thread类创建线程类示例

这篇文章主要介绍了Java继承Thread类创建线程类,结合实例形式分析了java线程操作相关使用技巧与注意事项,需要的朋友可以参考下
收藏 0 赞 0 分享

Java使用Callable和Future创建线程操作示例

这篇文章主要介绍了Java使用Callable和Future创建线程操作,结合实例形式分析了java使用Callable接口和Future类创建线程的相关操作技巧与注意事项,需要的朋友可以参考下
收藏 0 赞 0 分享

springBoot使用JdbcTemplate代码实例

这篇文章主要介绍了springBoot使用JdbcTemplate代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多