JUC专题-线程安全性之可见性有序性

1 可见性

1.1 可见性问题引出

来看一段代码:

arduino 复制代码
public class VolatileDemo {
    public static boolean stop=false;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            int i=0;
            while(!stop){
                i++;
            }
        });
        t1.start();
        System.out.println("begin start thread");
        Thread.sleep(1000);
        stop=true;
    }
}

是t1线程中用到了stop这个属性,接在在main线程中修改了 stop 这个属性的值来使得t1线程结束,但是t1线程并没有按照期望的结果执行,也就是这个代码永远不会结束,为什么出现了这个情况呢,其实就是main线程对stop的修改对t1线程不可见

1.2 了解可见性问题

我们先来看一张图:

计算机是利用CPU进行数据运算的,但是CPU只能对内存中的数据进行运算,对于磁盘中的数据,必须要先读取到内存,CPU才能进行运算,也就是CPU和内存之间无法避免的出现了IO操作。但是矛盾的是,Cpu、内存和磁盘三者的速度差异很大,自然而然就会有一个问题:cpu可能需要等待内存的iO,这样肯定是不合理的

为了平衡这三者之间的速度差异,最大化的利用CPU。所以在硬件层面、操作系统层面、编译器层面做出了很多的优化

  1. CPU增加了高速缓存
  2. 操作系统增加了进程、线程。通过CPU的时间片切换最大化的提升CPU的使用率
  3. 编译器的指令优化,更合理的去利用好CPU的高速缓存

每一种优化,都会带来相应的问题,而这些问题是导致线程安全性问题的根源,那接下来我们逐步去了解这些优化的本质和带来的问题。

1.2.1 CPU层面的缓存

第一个思路就是加缓存,例如我们平时开发的时候觉得数据库慢,也会将数据加载到redis进行读取

对于主流的x86平台,cpu的缓存行(cache)分为L1、L2、L3总共3级。

缓存一致性问题

在多线程环境中,当多个线程并行执行加载同一块内存数据时,由于每个CPU都有自己独立的L1、L2缓存,所以每个CPU的这部分缓存空间都会缓存到相同的数据,并且每个CPU执行相关指令时,彼此之间不可见,就会导致缓存的一致性问题,据图流程如下图所示:

而为了解决一致性问题,我们需要标记cpu缓存中数据的可用性,当线程1更新了a之后,那么线程2所读取的a就应该变为失效状态

简而言之: 如果添加了voliate,当数据被修改后,JVM会向CPU发送一条LOCK的前缀的指令,此时会将修改后的数据从缓存中同步到主内存中;当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效,由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取,进而保证了线程之间的可见性

那么肯定会有一个疑问,为什么不是所有字段都使用这样的方式从而保证可见性呢?

缓存的设计就是为了让cpu尽可能少的去读内存,频繁的缓存同步和主内存读写,会严重降低 CPU 缓存的效率

2 指令重排序代码

CPU在性能优化道路上导致的顺序一致性问题,在CPU层面无法被解决,原因是CPU只是一个运算工 具,它只接收指令并且执行指令,并不清楚当前执行的整个逻辑中是否存在不能优化的问题,也就是说 硬件层面也无法优化这种顺序一致性带来的可见性问题。

因此,在CPU层面提供了写屏障、读屏障、全屏障这样的指令,在x86架构中,这三种指令分别是 SFENCE、LFENCE、MFENCE指令, sfence:也就是save fence,写屏障指令。在sfence指令前的写操作必须在sfence指令后的写操作 前完成。 lfence:也就是load fence,读屏障指令。在lfence指令前的读操作必须在lfence指令后的读操作 前完成。 mfence:也就是modify/mix,混合屏障指令,在mfence前得读写操作必须在mfence指令后的读 写操作前完成。 在Linux系统中,将这三种指令分别封装成了, smp_wmb-写屏障 、 smp_rmb-读屏障 、 smp_mb-读写屏障 三 个方法

相关推荐
一 乐1 小时前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
码事漫谈2 小时前
Protocol Buffers 编码原理深度解析
后端
码事漫谈2 小时前
gRPC源码剖析:高性能RPC的实现原理与工程实践
后端
踏浪无痕4 小时前
AI 时代架构师如何有效成长?
人工智能·后端·架构
程序员小假4 小时前
我们来说一下无锁队列 Disruptor 的原理
java·后端
武子康5 小时前
大数据-209 深度理解逻辑回归(Logistic Regression)与梯度下降优化算法
大数据·后端·机器学习
maozexijr5 小时前
Rabbit MQ中@Exchange(durable = “true“) 和 @Queue(durable = “true“) 有什么区别
开发语言·后端·ruby
源码获取_wx:Fegn08956 小时前
基于 vue智慧养老院系统
开发语言·前端·javascript·vue.js·spring boot·后端·课程设计
独断万古他化6 小时前
【Spring 核心: IoC&DI】从原理到注解使用、注入方式全攻略
java·后端·spring·java-ee
毕设源码_郑学姐6 小时前
计算机毕业设计springboot基于HTML5的酒店预订管理系统 基于Spring Boot框架的HTML5酒店预订管理平台设计与实现 HTML5与Spring Boot技术驱动的酒店预订管理系统开
spring boot·后端·课程设计