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

原文地址:https://www.cnblogs.com/yufeng218/p/10116595.html