Java的Volatile实例用法及讲解

在原子性、可见性、有序性中,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指令实现

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

(0)

相关推荐

  • Java volatile的适用场景实例详解

    把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility). 原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护.从而防止多个线程在更新共享状态时相互冲突. 可见性则更为微妙,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的. -- 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题.

  • java volatile关键字作用及使用场景详解

    1. volatile关键字的作用:保证了变量的可见性(visibility).被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象.如以下代码片段,isShutDown被置为true后,doWork方法仍有执行.如用volatile修饰isShutDown变量,可避免此问题. public class VolatileTest3 { static class Work { boolean isShutDown = false; void shutdown(

  • 简单了解java volatile

    内存模型基本概念 计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度.因此在CPU里面就有了高速缓存. 并发编程中的三个概念 1、原子性 即一个操作或者多个操作,要么全部执行并且执行的过程不会被

  • 简单了解java volatile关键字实现的原理

    一.volatile关键字的语义分析 1.保证可见性 对共享变量的修改,其他线程能够马上感知到.但不能保证原子性(i++) 2.保证有序性 3.volatile的原理和实现机制 有volatile修饰的共享变量进行写操作的时候会多出有 "lock"标志的汇编代码,Lock前缀的指令在多核处理器下会引发两件事情: 1)将当前处理器缓存行中的数据写回到系统内存中 2)这个写回内存的操作会使在其他cpu里缓存了该内存地址的数据无效. 二.volatile的使用场景 1.状态标志(开关模式)

  • java同步之volatile解析

    问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java虚拟机提供的最轻量级的同步机制了,但是它并不容易被正确地理解,以至于很多人不习惯使用它,遇到多线程问题一律使用synchronized或其它锁来解决. 了解volatile的语义对理解多线程的特性具有很重要的意义,所以彤哥专门写了一篇文章来解释volatile的语义到底是什么. 语义一:可见性 前面

  • Java多线程之volatile关键字及内存屏障实例解析

    前面一篇文章在介绍Java内存模型的三大特性(原子性.可见性.有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇文章就来介绍volatile关键字的内存语义以及实现其特性的内存屏障. volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性:可见性和有序性.正因为volatile关键字具有这两大特性,所以我们可以使用volatile关键字解决多线程中的某些同步问题. volatile

  • Java的Volatile实例用法及讲解

    在原子性.可见性.有序性中,volatile关键字主要在可见性中发挥作用. volatile声明的变量对所有线程来说是可见的,就是说当变量的值发生改变的时候,其他线程可以立马发现这个变化. public class Main { private static boolean isRuning; private static int number; private static class ReaderThread extends Thread { public void run() { whil

  • java中throws实例用法详解

    在程序出现异常时,会有一个抛出异常的throw出现,这里我们要跟今天所讲的throws区分开.throws的作用是声明抛出,在名称上也跟throw有所不同.下面我们就throws对策概念.语法.实例带来讲解,帮助大家找到声明抛出异常的方法,具体方法如下. 1.概念 如果方法声明的是Exception类型的异常或者是Checked Exception异常,要求方法的调用处必须做处理. (1)继续使用throws向上(方法的调用处)声明 (2)使用try-catch-finally进行处理 2.语法

  • java中DelayQueue实例用法详解

    在阻塞队里中,除了对元素进行增加和删除外,我们可以把元素的删除做一个延迟的处理,即使用DelayQueue的方法.这里的删除需要一定的时间才能生效,有点类似于过期处理的理念.下面我们就DelayQueue的概念.特点进行讲解,然后在代码示例中体会DelayQueue的使用. 1.概念 是一个带有延迟时间的无界阻塞队列.队列中的元素,只有等延时时间到了,才能取出来.此队列一般用于过期数据的删除,或任务调度.以下,模拟一下定长时间的数据删除. 2.特点 (1)无边界设计 (2)添加(put)不阻塞,

  • binarySearch在java的查找实例用法

    在java数组中,查找数组元素是比较基础的操作了,arrays类的binarySearch就是专门实现指定元素的.同时它也属于我们常说的二分法.所以作用的范围是排序过的数组.下面我们就binarySearch的概念.使用注意进行说明,同时分出它的两种返回值情况,最后进行查找的实例分享. 1.概念 通过二分法在已经排好序的数组中查找指定的元素,并返回该元素的下标. 2.使用注意 此法为二分搜索法,故查询前需要用sort()方法将数组排序,如果数组没有排序,则结果是不确定的.如果数组中含有多个指定值

  • java短路逻辑运算符实例用法详解

    1.说明 逻辑操作符执行短路求值.所谓短路,就是当一个参与运算的操作数足以推断该表达式的值时,另一个操作数(可能是表达式)就不会执行. 在使用逻辑操作符时,当两个操作数都是true时,结果是true,但当第一个操作是false时,结果必须是false,此时不再判断第二个操作. 2.实例 public static void main(String[] args) { int a = 5;//定义一个变量: boolean b = (a < 4) && (a++ < 10); /

  • java中volatile不能保证线程安全(实例讲解)

    今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而不是从缓存中读取的数据,附上代码如下,当synchronized去掉的时候,每个线程的结果是乱的,加上的时候结果才是正确的. /** * * 类简要描述 * * <p> * 类详细描述 * </p> * * @author think * */ public class Volatil

  • java封装实例用法讲解

    我们可以选择把类的方法.属性装起来,便于日后的程序书写和使用,这种处理方法就是封装的思想.因为封装类之后,其他的外部类方法就不能在混入其中,对代码的安全性进行了提高.接下来我们就对java中封装的概念.目的进行介绍,然后在实例中为大家演示封装的方法. 1.概念 封装性是面向对象三大特征之一,是指一种将抽象性函式接口的实现细节部份包装.隐藏起来的方法.封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问. 2.目的 (1)辛苦一次,后面都能少敲很多代码,增强了代码得复用性

  • java SelectableChannel的使实例用法讲解

    1.说明 (1)SelectableChannel 是一个抽象类,它实现了 Channel 接口,这个类比较特殊. (2)SelectableChannel 可以被 Selector 用来多路复用,不过首先需要调用 selectableChannel.configureBlocking(false) 调整为非阻塞模式. 2.实例 SelectionKey register(Selector sel, int ops) SelectionKey register(Selector sel, int

  • java中volatile关键字的作用与实例代码

    一,什么是volatile关键字,作用是什么 volatile是java虚拟机提供的轻量级同步机制 ​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性 本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性 而在这之前需要对JMM 有所了解 二,什么是JMM ​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和

  • java中Calendar类用法实例详解

    本文实例讲述了java中Calendar类用法.分享给大家供大家参考,具体如下: java中的Calendar在开发中经常被忽略,这篇博客总结一下这个类,对后面项目中使用时期的时候有帮助. Calendar常量(field)的作用 Calendar cal = Calendar.getInstance(); cal.get(Calendar.DATE);//-----------------------当天 1-31 cal.get(Calendar.DAY_OF_MONTH);//------

随机推荐