• Volatile 关键字 内存可见性


    1、问题引入

    实现线程:

    public class ThreadDemo implements Runnable
    {
        private boolean flag = false;
    
        @Override
        public void run()
        {
            flag = true;
            System.out.println("flag=" + flag);
        }
    
        public boolean isFlag()
        {
            return flag;
        }
    
        public void setFlag(boolean flag)
        {
            this.flag = flag;
        }
    }

    测试类:

    public class VolatileTest
    {
        public static void main(String[] args)
        {
            ThreadDemo threadDemo = new ThreadDemo();
            new Thread(threadDemo).start();
    
            while (true)
            {
                if(threadDemo.isFlag())
                {
                    System.out.println("----------------");
                    break;
                }
            }
        }
    }

    结果:flag=true,并且程序不会停止

    结果分析从结果中看到,线程threadDemo 将 flag 被修改为 ture 了,但是 while 循环中的 if 判断中没有进入(即 flag = false);主线程中的flag和threadDemo 中的flag值不一样。

    内存可见性的问题:多个线程操作共享数据时,各个线程之间的数据不一致;

      JVM会为每个线程分配一个独立的缓存用于提高效率。

    (1)程序开始运行时主存中的flag=false;

    (2)接着线程threadDemo会修改flag数据:threadDemo先从主存中获取到flag数据(false),然后在线程threadDemo中的缓存数据修改为true;

    (3)在这个时候main线程来了,main线程从主存中读取到的flag=false;

    (4)线程threadDemo将flag=true写入到主存中;

    (5)while(true) 的执行效率特别高,以至于 main 线程没有机会从主存中读取数据;

    解决内存可见性的问题

    1、增加同步锁,用 synchronized 进行同步,代码如下:

    public class VolatileTest
    {
        public static void main(String[] args)
        {
            ThreadDemo threadDemo = new ThreadDemo();
            new Thread(threadDemo).start();
    
            while (true)
            {
                synchronized (threadDemo)
                {
                    if (threadDemo.isFlag())
                    {
                        System.out.println("----------------");
                        break;
                    }
                }
            }
        }
    }

    以上代码存在的问题:增加同步锁解决了内存可见性的问题,但是效率特别低;

    2、增加 volatile 关键字,修饰线程的共享数据,代码如下:

    public class ThreadDemo implements Runnable
    {
        private volatile boolean flag = false;
    
        @Override
        public void run()
        {
            flag = true;
            System.out.println("flag=" + flag);
        }
    
        public boolean isFlag()
        {
            return flag;
        }
    
        public void setFlag(boolean flag)
        {
            this.flag = flag;
        }
    }

     

     Volatile关键字不具备“互斥性”,Volatile不能保证“原子性”;

    Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
        所有的变量都存储在主内存中。每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝),如图

    两条规定:

    • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读取
    • 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

    在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这导致在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致。

    volatile关键字
      能够保证volatile变量的可见性,不能保证volatile变量复合操作的原子

    volatile如何实现内存的可见性:

    深入来说:通过加入内存屏障和禁止重排序优化来实现的
      在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
      在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

    通俗地讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。

    线程volatile变量的过程:
    (1)改变线程工作内存中volatile变量副本的值
    (2)将改变后的副本的值从工作内存刷新到主内存

    线程volatile变量的过程:
    (2)从主内存中读取volatile变量的最新值到线程的工作内存中
    (2)从工作内存中读取volatile变量的副本

     参考链接:

    https://www.cnblogs.com/amei0/p/8378625.html

  • 相关阅读:
    大型网站架构不得不考虑的10个问题
    js中settimeout方法加参数
    shell字符串操作详解
    linux shell 逻辑运算符、逻辑表达式
    c#判断网络连接状态示例代码
    asp.net读取excel文件多种方法
    asp.net导出excel示例代码
    php 数组排序代码
    php数组去重复代码
    php反射应用实例代码
  • 原文地址:https://www.cnblogs.com/yufeng218/p/10116595.html
Copyright © 2020-2023  润新知