ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Volatile详解

2022-03-04 21:01:14  阅读:114  来源: 互联网

标签:volatile 变量 关键字 详解 线程 内存 Volatile


一、Volatile介绍

  Volatile是Java并发编程十分常见的关键字,它能保证被修饰元素的可见效和有序性,具体介绍之前,先来写一点相关的知识。

二、Java内存模型

  java虚拟机有自己的内存模型(Java Memory Model,JMM),JMM可以屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。

  JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。这三者之间的交互关系如图

 

 

  总结来说,线程首先对本地内存中主内存元素的复制进行操作,等操作完成后在把新的值写回主内存。 而在这个过程中如果有其他线程读取来主内存的值,它是不知道这个值已经被修改了,因为还没有被修改的线程写回主内存。

三、深入理解Volatile

  被Volatile修饰的变量具有两个特性:

  • 可见性:可见性指的是当线程对一个Volatile变量进行修改后,其他线程能立刻知道这个值已经被修改,结合上面Java内存模型来说,当一个线程对Volatile变量进行修改后,会立即将修改后的指写入主内存,而不是保存在本地内存
  • 有序性 Volatile 可以禁止JVM对指令的重排

先来看一段代码:

  //线程2
1 boolean flag = true; 2 while(flag){ 3 4 } 5 //线程1 6 flag = false;

  假如线程2先执行,线程1后执行,再大部分情况下这段代码都会陷入死循环。

  而当我们将 flag 用 Volatile 修饰,就可以避免死循环的产生,为什么?

1、线程1修改 flag 的结果会立刻写入内存

2、当线程1修改完 flag 后,线程2的本地变量失效,反映到硬件就是 CPU 对应的L1或者L2缓存行失效

3、失效后线程2又会重新读取 flag 值,所以不会出现死循环

 

Volatile能保证原子性吗?

  先看一个例子:

 1 class Test {
 2     public volatile int inc = 0;
 3 
 4     public void increase() {
 5         inc++;
 6     }
 7 
 8     public static void main(String[] args) {
 9         final Test test = new Test();
10         for(int i=0;i<10;i++){
11             new Thread(){
12                 public void run() {
13                     for(int j=0;j<1000;j++)
14                         test.increase();
15                 };
16             }.start();
17         }
18 
19         while(Thread.activeCount()>1)  //保证前面的线程都执行完
20             Thread.yield();
21         System.out.println(test.inc);
22     }
23 }

这算一个经典的问题了,inc虽然是一个Volatile变量,但这个程序还是可能不能得到期望的结果,原因就在于 Volatile 并不能保证操作的原子性,特别是多元操作。

 

对于num++来说,底层是把它分为3步去做:

1、读取内存内num的指

2、进行+1操作

3、写入内存

  在多线程去执行这个操作的时候,有时候会出现当前线程虽然读取了num的值,还没有进行其他操作,其他线程已经将num的值更改了,导致读取不到正确的值。

这里可能有个疑问前面不是说如果进行修改了会使缓存的数据无效嘛?

  是会无效没错,但线程 1 进行读取后被阻塞还没有修改num的指,导致后来的线程2错误的认为没有被修改,所以线程 2 直接读取了内存的错误的指,导致了结果的错误,而这样的错误用 synchronized 和 Lock 都可以解决,所以 Volatile 是无法保证原子性的

 

Volatile能保证有序性是什么意思?

 

在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。

  volatile关键字禁止指令重排序有两层意思:

  1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

  2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

 

 

Volatile功能实现的原理:

前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。

  下面这段话摘自《深入理解Java虚拟机》:

 

  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

 

  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

 

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

 

  2)它会强制将对缓存的修改操作立即写入主存;

 

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

 

四、Volatile的使用场景

  synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

  1)对变量的写操作不依赖于当前值

  2)该变量没有包含在具有其他变量的不变式中

  实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

  事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

  下面列举几个Java中使用volatile的几个场景。

1、状态标记

 1 volatile boolean flag = false;
 2  
 3 while(!flag){
 4     doSomething();
 5 }
 6  
 7 public void setFlag() {
 8     flag = true;
 9 }
10 volatile boolean inited = false;
11 //线程1:
12 context = loadContext();  
13 inited = true;            
14  
15 //线程2:
16 while(!inited ){
17 sleep()
18 }
19 doSomethingwithconfig(context);

 

2、Double Check

 

 1 class Singleton{
 2     private volatile static Singleton instance = null;
 3      
 4     private Singleton() {
 5          
 6     }
 7      
 8     public static Singleton getInstance() {
 9         if(instance==null) {
10             synchronized (Singleton.class) {
11                 if(instance==null)
12                     instance = new Singleton();
13             }
14         }
15         return instance;
16     }
17 }

 



 

标签:volatile,变量,关键字,详解,线程,内存,Volatile
来源: https://www.cnblogs.com/kxxiaomutou/p/15964619.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有