首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[Java] volatile 详详解![通俗易懂]

[Java] volatile 详详解![通俗易懂]

作者头像
全栈程序员站长
发布于 2022-09-08 03:10:12
发布于 2022-09-08 03:10:12
20900
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

前言

要真正搞懂volatile的特性需要与JMM对比来看

JMM(线程安全的保证)

JMM:JAVA内存模型(java memory model) 是一种抽象概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM关于同步的规定

  • 线程解锁前,必须把共享变量的值刷新回主内存;
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存;
  • 枷锁解锁是同一把锁。

由于JVM运行程序的实体是线程。而每个线程创建时JVM都会为其创建一个工作内存,工作内存是每个线程的私有数据区域。 JMM中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读写)必须在工作内存中进行。具体步骤:首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存存储着主内存中的变量副本拷贝,因此不同线程间无法访问对方的工作内存,线程的通信必须通过主内存来完成

如图所示 可以说cache缓存 就是 这种JMM内存模型的硬件抽象

JMM的特性:可见性,原子性,有序性。

说回到volatile

volatile

volatile 是java虚拟机提供的轻量级同步机制 导致并发问题的源头是 : 多核 CPU 缓存导致程序的可见性问题、多线程间切换带来的原子性问题以及编译优化带来的顺序性问题。

下面是三个volatile特性

可见性

用代码证明volatile的可见性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyDate { 
   
    //共享变量 1.1 首先不加volatile关键字
    int number = 0;

    public void change() { 
   
        this.number = 60;
    }
}

/** * 1.验证volatile的可见性 */
public class VolatileDemo { 
   
    public static void main(String[] args) { 
   
        MyDate myDate = new MyDate();
        new Thread(() -> { 
   
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try { 
   
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            myDate.change();
            System.out.println(Thread.currentThread().getName() + "\t update number value=" + myDate.number);
        }, "A线程").start();

        //main线程读到的数据
        while (myDate.number == 0) { 
   
            //main 线程一直循环等待直到number值不等于0
        }
        System.out.println(Thread.currentThread().getName() + "\t over number value="+myDate.number);
    }
}

首先new了一个名字叫A线程的线程。先让线程sleep三秒钟,此时A线程和main线程都拿到了number的值。然后A线程修改了number的值为60。但是main线程因为没有可见性不知道值发生了修改。还是继续循环等待,证明了此时线程之间没有可见性。

1.2 证明volatile可见性 ,我们在 int number前面加上volatile关键字

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
volatile int number = 0;

此时main线程由可见性及时知道了主内存中的number值发生了修改,跳出了循环。

不保证原子性

代码证明不保证原子性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyDate { 
   
    volatile int number = 0;
    public void add() { 
   
        number++;
    }
}

//2.1证明原子性
//原子性是指 完整性,不可分割,就是说某个线程执行时不可分割或被插队。
public class VolatileDemo { 
   
    public static void main(String[] args) { 
   
        MyDate myDate = new MyDate();
        for (int i = 0; i < 10; i++) { 
   
            new Thread(() -> { 
   
                for (int j = 0; j < 1000; j++) { 
   
                    myDate.add();
                }
            }, String.valueOf(i)).start();
        }
        // 等待线程计算完成后 由main线程取最终结果 10*1000 = 1万
        while (Thread.activeCount()>2){ 
    //mian线程和GC后台线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t sum value="+myDate.number);
    }

代码for出10个线程,每个线程做1000次add操作。正常来说10个线程操作完成之后number应该等于10000

但是程序没次运行都会得到不同的结果,而且小于10000. 是由于number++这个操作在java中是线程不安全的。

n++这个操作在字节码中分为了三个步骤

getfield —- 拿到原始的值n iadd ——– 进行+1操作 putfield —–把累加后的值写回

当在多线程环境中,如果多个线程都拿到了原始的n,运算完成后在写回数值之前线程被挂起或阻塞,线程恢复后来不及收到可见性通知就向主内存写下了数值,就会发生写覆盖的情况,丢失数据。

导致了在本程序中无法达到10000.

如何解决呢

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public synchronized void add() { 
   
        number++;
    }

首先可能想到的就是synchronized同步方法,但是synchronized太重了,一个方法里面就一个++操作如果用synchronized就好比杀鸡用牛刀,大炮打蚊子。需要功能和性能的同时考虑。

使用java.util.concurrent.atomic下的AtomicInteger类

原子性的增加1 相当于++操作

或者getAndAdd(int delta) 参数填1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 //2.3 原子性int
    AtomicInteger atomicInteger = new AtomicInteger();
    // 原子性方法
    public void addWithAtomic(){ 
   
        atomicInteger.getAndIncrement();
    }

至于为什么AtomicInteger能保证原子性,后面有机会继续详解。

禁止指令重排

指令重排:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为编译器优化重排,指令并行的重排,内存系统的重排。

在多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的共享变量的一致性时无法保证的。所以结果无法预测。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int a = 0;
    boolean flag = false;

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

    public void reader() { 
   
        if (flag) { 
        //语句3
            a = a + 1;  //语句4
            System.out.println("value:" + a);
        }
    }

volatile 实现了禁止指令重排序优化,从而避免了多线程环境下程序出现乱序执行的现象

实现:在对volatile变量进行写操作时,会在写操作后面加入一条Memory Barriier(内存屏障)告诉内存和CPU,禁止在内存屏障前后的执行指令重排优化

volatile 使用场景

DCL(双重检查锁)+ volatile的单例模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Singleton { 
   
    private static Singleton instance = null;
    // + volatile 👆
    private Singleton() { 
   
        System.out.println(Thread.currentThread().getName() + "\t Singleton的构造方法");
    }

    //DCL Double check lock
    public static Singleton getInstance() { 
   
        if (instance == null) { 
   
            synchronized (Singleton.class) { 
   
                if (instance == null) { 
   
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

如果不加volatile

由于

instance = new Singleton();

要分为3步骤执行

1.分配对象内存空间 2.初始化对象 3.完成对象的引用

步骤2和步骤3不存在数据依赖关系,所以可能发生重排序,1->3->2 此时的引用对象可能还没有完成初始化

当一条线程访问instance不为null时,由于instance实例还没有初始化完成,就造成了线程安全问题。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156478.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java volatile 关键字详解「建议收藏」
​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式.
全栈程序员站长
2022/09/09
2850
volatile 详解
JMM本身是一种抽象的概念模型并不真实存在,塔描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段、静态字段、构成数组对象的元素)的访问方式
ruochen
2021/12/16
1.6K0
深入理解JUC:第一章:volatile的三大特性
主内存:就是你买电脑选择8G内存,这个就是你的主内存,也是你new 一个对象存放的地方,是共享内存区域,所有线程都可以访问,java内存模型中规定所有不了存储在主内存里。
Java廖志伟
2022/09/28
2280
深入理解JUC:第一章:volatile的三大特性
Java基础:volatile详解
问:请谈谈你对volatile的理解? 答:volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性: 1)保证可见性 2)不保证原子性 3)禁止指令重排
全栈程序员站长
2022/09/09
2140
JUC系列(八)Java内存模型 volatile关键字与单例模式实践
JMM就是Java内存模型(java memory model) Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
冷环渊
2022/12/03
3970
JUC系列(八)Java内存模型 volatile关键字与单例模式实践
volatile详解
在单线程环境中,我们几乎用不到这个关键词,但是多线程环境中,这个关键词随处可见。而且也是面试的常客。总的来说,volatile有以下三个特性:
贪挽懒月
2019/05/07
6010
volatile详解
Java多线程编程中之volatile详解
在Java多线程编程中,volatile关键字是一种重要的同步机制,可以理解为低配版synchronized,轻量级的同步策略,保证可见性,不保证原子性,禁止指令重排。它用于确保多线程环境下变量的可见性和顺序性。通过使用volatile关键字,可以避免线程之间的竞争条件和数据不一致性问题。本文将详细解释Java中的volatile关键字以及它在多线程编程中的应用。
小明爱吃火锅
2023/11/02
1.4K0
Java多线程编程中之volatile详解
反制面试官 | 14张原理图 | 再也不怕被问 volatile!
这一篇也算是Java并发编程的开篇,看了很多资料,但是轮到自己去整理去总结的时候,发现还是要多看几遍资料才能完全理解。还有一个很重要的点就是,画图是加深印象和检验自己是否理解的一个非常好的方法。
悟空聊架构
2020/08/19
3720
java面试题:谈谈你对volatile的理解
  最近打算整理下Java面试中频率比较高,相对比较难的一些面试题,感兴趣的小伙伴可以关注下。
用户4919348
2020/04/16
1.2K0
JUC并发编程(二)认识volatile,单例模式,各种锁
JMM : Java内存模型,不存在的东西,概念!约定! 关于JMM的一些同步的约定: 1. 线程解锁前,必须把共享变量立刻刷回主存。 2. 线程加锁前,必须读取主存中的新值到工作内存中! 3. 加锁和解锁是同一把锁
HcodeBlogger
2020/07/14
3430
JUC并发编程(二)认识volatile,单例模式,各种锁
【JUC】009-ForkJoin分支合并、异步回调、JMM、Volatile关键字、指令重排
Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行;
訾博ZiBo
2025/01/06
1110
【JUC】009-ForkJoin分支合并、异步回调、JMM、Volatile关键字、指令重排
【并发编程】1 synchronized底层实现原理、Java内存模型JMM;monitor、CAS、乐观锁和悲观锁;对象的内存结构、Mark Word、锁升级
本文为 1~4小节,5、6节请查阅【并发编程】2 synchronized底层实现原理、Java内存模型JMM;monitor、CAS、乐观锁和悲观锁;对象的内存结构、Mark Word、锁升级
寻求出路的程序媛
2024/05/12
1950
【并发编程】1 synchronized底层实现原理、Java内存模型JMM;monitor、CAS、乐观锁和悲观锁;对象的内存结构、Mark Word、锁升级
(十四)volatile的用法,原子性问题
synchronized是阻塞式同步,会在线程竞争激烈的情况下,会升级为重量级锁,还可能会死锁;而volatile是一种轻量级的同步机制。
HaC
2020/12/30
7720
(十四)volatile的用法,原子性问题
谈谈Java中的volatile
在《死磕GOF23种设计模式之单例模式》中,其中双重检查锁使用到了volatile关键字,本篇文章就带大家深入了解一下volatile相关的知识。
程序新视界
2022/05/09
2500
你应该知道的 volatile 关键字
由于 Java 内存模型( JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
crossoverJie
2022/08/19
1680
你应该知道的 volatile 关键字
Java的volatile关键字详解
在学习ConcurrentHashMap源码的过程中,发现自己对并发编程简直是一无所知,因此打算从最基础的volatile开始学习.
呼延十
2019/07/01
6420
Java的volatile关键字详解
Volatile关键字能保证原子性么?
说到这个 volatile 这个关键字,阿粉觉得看过阿粉文章的,肯定都对这个关键字那是非常的熟悉的,因为做Java开发的,在面试的时候,如果涉及到多线程,那么面试官有不少人会询问关于 volatile 这个关键字的使用,以及他的作用,今天阿粉就来说说这个 volatile 关键的的作用,以及他的一些特性。
Java极客技术
2022/12/04
4210
Volatile关键字能保证原子性么?
【小家java】使用volatile关键字来实现内存可见性、实现轻量级锁
volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。
YourBatman
2019/09/03
6330
【小家java】使用volatile关键字来实现内存可见性、实现轻量级锁
【进击面试_03】Java 并发
☞ JMM 是什么   JMM(Java 内存模型:Java Memory Model,简称 JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,定义了程序中各个共享变量的访问规则,即在虚拟机中将变量存储到内存和从内存读取变量这样的底层细节。   根据 JMM 的设计,系统存在一个主内存(Main Memory),Java 中所有实例变量都储存在主存中,对于所有线程都是共享的。每个线程都有自己的工作内存(Working Memory)是私有数据区域。线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝。不同的线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成。
Demo_Null
2021/03/04
3160
【进击面试_03】Java 并发
volatile 关键字了解与使用
由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
大道七哥
2019/08/23
2940
volatile 关键字了解与使用
推荐阅读
相关推荐
java volatile 关键字详解「建议收藏」
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验