• volatile关键字详解


    volatile的作用

            volatile是一个轻量级的synchronized,一般作用于变量,在多线程开发中保证了内存的可见性。相比于synchronized关键字,volatile关键字的执行成本更低,效率更高。

     volatile的特性

    多线程三大特性:原子性、可见性、有序性;

    • 原子性: 是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
    • 可见性: 是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行来说,可见性问题是不存在的。
    • 有序性: 在并发时,程序的执行可能会出现乱序,给人的直观感觉就是写在前面的代码,会在后面执行,究其原因是指令重排导致的。

    volatile具有三大特性:

    1.可见性

            volatile可以保证不同线程对共享变量进行操作时的可见性,即当一个线程修改了共享变量时,另一个线程可以读取到共享变量被修改后的值。

            导致内存不可见的主要原因就是Java内存模型中的本地内存和主内存之间的值不一致所导致,例如线程A访问自己本地内存A的X值时,但此时主内存的X值已经被线程B所修改,所以线程A所访问到的值是一个脏数据。
            volatile可以保证内存可见性的关键是volatile的读/写实现了缓存一致性,缓存一致性的主要内容为:

            每个处理器会通过嗅探总线上的数据来查看自己的数据是否过期,一旦处理器发现自己缓存对应的内存地址被修改,就会将当前处理器的缓存设为无效状态。此时,如果处理器需要获取这个数据需重新从主内存将其读取到本地内存。·当处理器写数据时,如果发现操作的是共享变量,会通知其他处理器将该变量的缓存设为无效状态。

    那缓存一致性是如何实现的呢?可以发现通过volatile修饰的变量,生成汇编指令时会比普通的变量多出一个Lock 指令,这个Lock指令就是volatile关键字可以保证内存可见性的关键,它主要有两个作用:
    1.将当前处理器缓存的数据刷新到主内存。
    2.刷新到主内存时会使得其他处理器缓存的该内存地址的数据无效。

    2.有序性

            volatile通过禁止指令重排来保证代码的执行顺序。

            为了实现volatile的内存语义,编译器在生成字节码时会通过插入内存屏障来禁止指令重排序。
            内存屏障:内存屏障是一种CPU指令,它的作用是对该指令前和指令后的一些操作产生一定的约束,保证一些操作按顺序执行。

    3.不能保证原子性

    1. class VolatoleAtomicityDemo {
    2. public volatile static int inc = 0;
    3. public void increase() {
    4. inc++;
    5. }
    6. public static void main(String[] args) throws InterruptedException {
    7. ExecutorService threadPool = Executors.newFixedThreadPool(5);
    8. VolatoleAtomicityDemo volatoleAtomicityDemo = new
    9. VolatoleAtomicityDemo();
    10. for (int i = 0; i < 5; i++) {
    11. threadPool.execute(() -> {
    12. for (int j = 0; j < 500; j++) {
    13. volatoleAtomicityDemo.increase();
    14. }
    15. });
    16. }
    17. // 等待1.5秒,保证上⾯程序执⾏完成
    18. Thread.sleep(1500);
    19. System.out.println(inc);
    20. threadPool.shutdown();
    21. }
    22. }
    正常情况下,运⾏上⾯的代码理应输出 2500 ,但实际每次输出结果都⼩于 2500
    如果 volatile 能保证 inc++ 操作的原⼦性的话。每个线程中对 inc 变量⾃增完之后, 其他线程可以⽴即看到修改后的值。 5 个线程分别进⾏了 500 次操作,那么最终 inc 的值应该是5*500=2500
    但是   inc++ 其实是⼀个复合操作,包括三步:
    1. 读取 inc 的值。
    2. inc 1
    3. inc 的值写回内存。
    volatile 是⽆法保证这三个操作是具有原⼦性的,有可能导致下⾯这种情况出现:
    1. 线程 1 inc 进⾏读取操作之后,还未对其进⾏修改。线程 2 ⼜读取了 inc 的值并对其进⾏
    修改( +1 ),再将 inc 的值写回内存。
    2. 线程 2 操作完毕后,线程 1 inc 的值进⾏修改( +1 ),再将 inc 的值写回内存。
    这也就导致两个线程分别对 inc 进⾏了⼀次⾃增操作后, inc 实际上只增加了 1
    其实,如果想要保证上⾯的代码运⾏正确也⾮常简单,利⽤ synchronized Lock 或者 AtomicInteger 都可以。
  • 相关阅读:
    数据库pymsql之使用简单登陆注册功能实现
    解决vscode内置视图npm脚本操作报权限问题
    ansible部署MySQL主从
    Win,M1Mac上安装jupyter的MATLAB支持插件的方法
    Java 基础之锁
    手写Demo体验volatile可见性的作用
    详细讲解仪器仪表modbus RTU或TCP 获取的16位数字转浮点数 附c#代码
    axios的post请求所有传参方式
    【毕业季】这四年一路走来都很值得——老学长の忠告
    AIGC实战——变分自编码器(Variational Autoencoder, VAE)
  • 原文地址:https://blog.csdn.net/qq_41573860/article/details/126705381