写点什么

数据的强一致性与弱一致性 (1)

用户头像
Android架构
关注
发布于: 5 小时前

}}


如果两个线程同时运行,两个线程的变量的值可能会出现以下三种结果:


  • 1,1

  • 2,1

  • 1,2


2,1 和 1,2 的结果我们很好理解,那为什么会出现以上 1,1 的结果呢?


在解释为什么会出现这样的结果之前,我们先通过下图来简单了解下 Java 的内存模型

Java 内存模型

Java 采用共享内存模型来实现多线程之间的信息交换和数据同步。程序在运行时,局部变量将会存放在虚拟机栈中,而共享变量将会被保存在堆内存中。



由于局部变量是跟随线程的创建而创建,线程的销毁而销毁,所以存放在栈中,由上图我们可知,Java 栈数据不是所有线程共享的,所以不需要关心其数据的一致性。


共享变量存储在堆内存或方法区中,由上图可知,堆内存和方法区的数据是线程共享的。而堆内存中的共享变量在被不同线程操作时,会被加载到自己的工作内存中,也就是 CPU 中的高速缓存。



CPU 缓存可以分为一级缓存(L1)、二级缓存(L2)和三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分。当 CPU 要读取一个缓存数据时,首先会从一级缓存中查找;如果没有找到,再从二级缓存中查找;如果还是没有找到,就从三级缓存或内存中查找。


如果是单核 CPU 运行多线程,多个线程同时访问进程中的共享数据,CPU 将共享变量加载到高速缓存后,不同线程在访问缓存数据的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。


如果是多核 CPU 运行多线程,每个核都有一个 L1 缓存,如果多个线程运行在不同的内核上访问共享变量时,每个内核的 L1 缓存将会缓存一份共享变量。


假设线程 A 操作 CPU 从堆内存中获取一个缓存数据,此时堆内存中的缓存数据值为 0,该缓存数据会被加载到 L1 缓存中,在操作后,缓存数据的值变为 1,然后刷新到堆内存中。


在正好刷新到堆内存中之前,又有另外一个线程 B 将堆内存中为 0 的缓存数据加载到了另外一个内核的 L1 缓存中,此时线程 A 将堆内存中的数据刷新到了 1,而线程 B 实际拿到的缓存数据的值为 0。


此时,内核缓存中的数据和堆内存中的数据就不一致了,且线程 B 在刷新缓存到堆内存中的时候也将覆盖线程 A 中修改的数据。这时就产生了数据不一致的问题。

解释 1,1 的结果

了解完内存模型之后,结合以下图,我就可以理解 1,1 的运行结果了。


重排序

在 Java 内存模型中,还存在重排序的问题。请看以下代码:


//代码 1public class Demo {int x = 0;boolean flag = false;public void writer() {x = 1;


flag = true;


}


public void reader() {if (flag) {


int r1 = x;


System.out.println(r1==x)}}}


如果两个线程同时运行,线程


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


1 调用 writer 方法,线程 2 调用 reader 方法,线程 2 中的 r1 变量的值可能会出现以下两种可能:


r1=0 或者 r1=1


现在一起来看看 r1=1 的运行结果,如下图所示:



那 r1=0 又是得到的?我们再来看以下入:



所以在 JVM 中,重排序是十分重要的一环,特别是在并发编程中。课时 JVM 要是能对它们进行任意排序的话,也可能会给并发编程带来一系列的问题,其中就包括了一致性的问题。


为了解决这个问题,Java 提出了 Happens-before 规则来规范线程的执行顺序:

Happens-before 规则

**程序次序规则:**在单线程中,代码的执行是有序的,虽然可能会存在运行指令的重排序,但最终执行的结果和顺序执行的结果是一致的;


**锁定规则:**一个锁处于被一个线程锁定占用状态,那么只有当这个线程释放锁之后,其它线程才能再次获取锁操作;


**volatile 变量规则:**如果一个线程正在写 volatile 变量,其它线程读取该变量会发生在写入之后;


**线程启动规则:**Thread 对象的 start() 方法先行发生于此线程的其它每一个动作;

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
数据的强一致性与弱一致性(1)