Java内存模型-volatile内存语义

简介: 章节目录1.volatile 的特性为什么volatile修饰变量的写操作不是原子性的?2.volatile 写-读建立的 happens-before 关系3.volatile 写-读的内存语义1.volatile 的特性首先应该明确的一点是:当声明共享变量为volatile后,对这个变量的读/写(分为单元素读写,与复合写操作)。

章节目录

  • 1.volatile 的特性
    • 为什么volatile修饰变量的写操作不是原子性的?
  • 2.volatile 写-读建立的 happens-before 关系
  • 3.volatile 写-读的内存语义

1.volatile 的特性

首先应该明确的一点是:当声明共享变量为volatile后,对这个变量的读/写(分为单元素读写,与复合写操作)。不同的读写模式下,volatile变量对写操作的原子性体现是不一样的。

理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是同一个锁对这些单个读/写操作做了同步,示例代码如下所示:

class VolatileFeaturesExample {
   volatile long vl = 0L;
   public void set(long l){
      vl = l;
   }

   public void getAndIncrement() {
       vl++;
   }

  public long get(){
     return vl;
  }
}

假设有多个线程调用上述程序中的3个方法,这个程序语义和下面的程序语义等价

class VolatileFeaturesExample {
   long vl = 0L;
   public  synchronized void set(long l){
      vl = l;
   }

   public void getAndIncrement() {
       long temp = get();
       temp +=1L;
       set(temp);
   }

  public syntronized long get(){
     return vl;
  }
}

解释

  • 如这两个程序所示,一个volatile 变量的单个读/写操作,与一个普通变量的读/写操作都是使用同一个锁来同步,他们之间的执行效果相同。
  • 锁的happens-before规则(保证前一个操作结果对后一个操作立即可见)保证释放锁和获取锁的两个线程之间的内存可见性。这很好的解释了对于一个volatile变量的读,总能看到任意线程对这个volatile变量最后的写入。
  • 锁的语义决定了临界代码区的执行具有原子性。这意味着,即使是64位的long型和double型变量,只要它是volatile变量,对该变量的单个读/写就具有原子性,如果是多个volatile操作,或这是volatile++这种复合操作,这些操作在整体上是不具有原子性的。

volatile 自身特性:

1.内存可见性:对一个volatile变量的读,总能看到任意线程对这个volatile变量
  最后的写入(内存可见性保证)
2.原子性:对任意单个volatile变量的单独的读写操作,都具有原子性。但对于
  volatile++这种复合操作不具有原子性。

2.volatile 写-读建立的 happens-before 关系

JMM基于共享内存模型实现线程之间的通信

从jdk1.5 开始 volatile 变量的写-读可以实现线程之间的通信。

volatile & synchronized内存语义

从内存语义来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile
写和锁释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。

下面为volatile变量示例代码:

class VolatileExample {
    int a = 0;//其实是线程共享变量
    volatile boolean flag = false;

    public  void writer(){
        a = 1;                   //1
        flag = true;             //2
    }

   public void reader(){
       if(flage){                //3
         int i = a;              //4
         ......
      }
   }
}

假设线程A 执行 writer() 方法之后,线程B 执行reader() 方法。根据happens-before 规则,这个过程建立的happens-before规则可以分为三类:
1.根据程序次序规则,1 happens before 2;3 happens before 4.
2.根据volatile规则,2 happens-before 3
3.根据happens-before c传递性规则 1 happens-before 4

图形化形势如下图所示:

img_0d8d80545ee12cce05a4eda864ca0e46.png
volatile-写-读 happens-before 关系

A线程在写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile之前所有的可见共享变量,在B线程读到同一个volatile变量之后,将立即变得对B线程可见。

4. volatile 写-读的内存语义

** volatile 写内存语义 **

当写一个volatile变量时,JMM会把该线程对应的本地内存的变量中的变量值强制刷新到主内存。

volatile 读的内存语义

 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

volatile内存语义 总结

  • 线程A 写一个volatile 变量,实质上是线程A 向接下来读取这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
  • 线程B 读一个volatile 变量,实质上是线程B 接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改)消息。
  • 线程A 写一个volatile 变量,随后线程B读这个volatile变量。这个过程实质上是线程A通过主内存向线程B发送消息。
目录
相关文章
|
12天前
|
存储 Java
深入理解Java虚拟机:JVM内存模型
【4月更文挑战第30天】本文将详细解析Java虚拟机(JVM)的内存模型,包括堆、栈、方法区等部分,并探讨它们在Java程序运行过程中的作用。通过对JVM内存模型的深入理解,可以帮助我们更好地编写高效的Java代码,避免内存溢出等问题。
|
15天前
|
算法 Java Go
Go vs Java:内存管理与垃圾回收机制对比
对比了Go和Java的内存管理与垃圾回收机制。Java依赖JVM自动管理内存,使用堆栈内存并采用多种垃圾回收算法,如标记-清除和分代收集。Go则提供更多的手动控制,内存分配与释放由分配器和垃圾回收器协同完成,使用三色标记算法并发回收。示例展示了Java中对象自动创建和销毁,而Go中开发者需注意内存泄漏。选择语言应根据项目需求和技术栈来决定。
|
4天前
|
存储 算法 Java
了解Java内存管理与垃圾回收机制
了解Java内存管理与垃圾回收机制
7 0
|
13天前
|
存储 机器学习/深度学习 Java
【Java探索之旅】数组使用 初探JVM内存布局
【Java探索之旅】数组使用 初探JVM内存布局
26 0
|
3月前
|
存储 安全 Java
[Java]volatile关键字
[Java]volatile关键字
31 0
|
3月前
|
存储 缓存 Java
Java volatile关键字-单例模式的双重锁为什么要加volatile
Java volatile关键字--单例模式的双重锁为什么要加volatile
53 10
|
5月前
|
Java 编译器
Java多线程:什么是volatile关键字?
Java多线程:什么是volatile关键字?
30 0
|
5月前
|
SQL 缓存 安全
java中volatile关键字
java中volatile关键字
41 0
java中volatile关键字
|
10月前
|
安全 Java
Java中的Volatile关键字
Java中的Volatile关键字
|
4月前
|
Java
多线程与并发编程:解释什么是死锁,并给出一个在Java中发生死锁的例子。描述一下Java中的volatile关键字的作用,以及它与synchronized的区别。
多线程与并发编程:解释什么是死锁,并给出一个在Java中发生死锁的例子。描述一下Java中的volatile关键字的作用,以及它与synchronized的区别。
29 0