zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    几种常用的曲线
    0188. Best Time to Buy and Sell Stock IV (H)
    0074. Search a 2D Matrix (M)
    0189. Rotate Array (E)
    0148. Sort List (M)
    0859. Buddy Strings (E)
    0316. Remove Duplicate Letters (M)
    0452. Minimum Number of Arrows to Burst Balloons (M)
    0449. Serialize and Deserialize BST (M)
    0704. Binary Search (E)
  • 原文地址:https://www.cnblogs.com/yufeng218/p/10116595.html
Copyright © 2011-2022 走看看