Java内存模型及线程可见性问题

 计算机是一门实践性很强的学科,如果我们一上来就讲理论,大概率会让人看的云里雾里,这里我们先看一段程序。

/**
 * Java内存模型(可见性)
 * @author youyun.xu
 * @Date 2022-03-17 23:34
 * **/
public class JmmDemo {

    public static boolean flag = false;

    /**
     * 期望结果:thread1 在执行死循,每次循环都在判断flag是否为true,如果为true则跳出死循环。否则一直等待。
     *          thread2 修改flag为true时,thread1 应该读取最新值后跳出死循环,继续往下执行
     * 实际结果:thread2 修改flag为true
     *          thread2 无法读取到flag最新值,无法跳出死循环
     * **/
    public static void main(String [] args) {
        System.out.println("Simulate the synchronization mechanism of main memory and working memory in multithreaded environment - > start 2 threads");
        //线程1
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("thread 1 start execution");
                while (!flag){
                    //work
                }
                System.out.println("thread 1 end execution");
            }
        });
        thread1.start();

        //休眠1秒
        try {
            Thread.sleep(1000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //线程2
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("thread 2 start execution");
                flag = true;
                System.out.println("thread 2 end execution");
            }
        });
        thread2.start();
    }
}

这段程序并不复杂,程序主线程中启动了2个异步线程,thread1 每次循环都判断flag是否为true,如果为ture则跳出死循环,继续往下执行。而thread2负责修改flag为ture。

我们根据上面的伪代码在脑海中模拟一下运行结果,看运行结果会不会如下面所示

Simulate the synchronization mechanism of main memory and working memory in multithreaded environment - > start 2 threads
thread 1 start execution
thread 2 start execution
thread 2 end execution
thread 1 end execution
  • thread1 在执行死循,每次判断flag是否为true,如果为true则跳出死循环,继续往下执行
  • thread2 修改 flag = true,thread1线程获取最新修改值,跳出死循环

实际上我们通过程序运行发现,程序输出结果是这样的

Simulate the synchronization mechanism of main memory and working memory in multithreaded environment - > start 2 threads
thread 1 start execution
thread 2 start execution
thread 2 end execution

当thread2 修改 flag = true时,thread1并没有跳出死循环,“thread 1 end execution” 并没有打印。程序依然无法跳出死循环。

从运行结果中我们可以发现,先thread 2 修改了flag的值为true,对thread 1线程是感知不到的 ,这就是一个面试中常问的一个线程"可见性"问题。产生这个“”可见性“”问题的就工作内存和主内存同步不一致导致的。

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。

我们结合Java内存模型简写为JMM(Java Memory Model)和上面程序源代码画一张图来说明工作内存和主内存是如何完成数据同步的

  • thread 1 和 thread 2 通过原子操作read 从主内存中读取flag,然后通过load 装载到工作内存
  • thread 1 读取原子操作use使用flag变量,执行循环。
  • thread 2 修改flag变量为true,通过并通过原子操assign重新赋值到工作内存中,然后通过原子操作store把最新修改的值同步到主内存。然后通过原子操作write 将store过去的变量值赋值给主内存中的变量

Java内存模型定义了8种操作来完成工作内存和主内存的同步。这8种操作每一种都是原子操作。8种操作如下:

  • read(读取):从主内丰读取数据
  • load(载入):将主内存读取到的数据写入工作内存
  • use(使用):从工作内存读取数据来计算
  • assign(赋值):将计算好的值重新赋值到工作内存中
  • store(存储):将工作内存数据写入主内存
  • write(写入):将store过去 的变量值赋值给主内存中的变量
  • lock(锁定):将主内存变量加锁,标识为线程独占状态
  • unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量

从图我们可以发现一个线程对共享变量做了修改之后,虽然刷新到主内存中,但thread1没有再次从主内存中读取最新的值,而是依然用的工作内存中的旧的值,导致无法跳出死循环。

什么是线程可见性及如何保证

一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。

保证线程可见性有很多方式,比如在变量修饰上修饰关键字 volatile

    public static volatile boolean flag = false;

Volatile可见性实现原理 :volatile的特殊规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。

底层实现主要是通过 汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存,IA-32架构软件开发者手册对lock指令的理解:

1)会将当前处理器缓存行为的数据立即写回到系统内存

2)这个写回内存的操作会引起在其他CPU里缓存了该 内存地址的数据无效(MESI协议)

除了volatile关键字能实现可见性之外,还有synchronized、Lock,final也是可以的

总线加锁 synchronized、Lock(性能太低)

  CPU从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其它CPU没法去读或写这个数据,直到这个CPU使用完数据释放锁之后其它CPU才能读取该数据

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热情的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值