• 多线程的实现原理


    JMM怎么解决原子性、可见性、有序性的问题?

    在java中提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、juc等,这些就是java内存模型封装了底层的实现后提供给开发人员使用的关键字,在开发多线程代码的时候,我们可以直接使用synchronized等关键词来控制并发,使得我们不关心底

    层的编译器优化、缓存一致性的问题了,所以在java内存模型中,除了定义了一套规范,还提供了开放的指令在底层进行封装后,提供给开发人员使用。

    原子性:

    在java中提供了两个高级的字节码指令monitorenter和monitorexit,在java中对应的synchronized来保证代码块内的操作是原子性的。

    可见性:

    在java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

    除了volatile,java中synchronized和final两个关键字也可以实现可见性。

    有序性:

    在java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

    volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

    volatile如何保证可见性

    Volatile 修饰共享变量,在进行写操作时会多出一个lock前缀的汇编指令,会触发总线锁和缓存锁,通过缓存一致性来解决可见性问题;

    对于声明了volatile变量进行写操作,jvm会向处理器发送一条lock前缀的指令,把这个变量所在的缓存行数据写到系统内存里,在根据 Mesi缓存一致性协议,来保证多cpu下各个高速缓存的数据一致性。

    volatile如何防止指令重排序

    指令重排目的是为了提高cpu的利用率以及性能,cpu的乱序执行优化在单核的时代并不影响正确性,但是在多核时代的多线程能够在不同的 核心上真正实现并行,一旦线程之间共享数据,可能会出现一些不可预料的问题;

    我们可以优化屏障和内存屏障来解决这个问题,一个是编译器的优化乱序和CPU的执行乱序;

    从CPU层面来看,什么是内存屏障?

    CPU的乱序执行,本质还是由于多CPU的机器上,每个CPU都有一个cache,当一个特定数据第一次被特定的cpu获取时,由于CPU缓存中不存在,就会到内存中去获取,被加载到高速缓存中后就能够从缓存中快速访问;当某个CPU在进行写操作后,要确保其他CPU已经将这个数据从缓存中移除,这样才能让其他CPU安全的修改数据。显然,存在多个cache时,我们必须通过cache一致性协议来避免数据不一致性的问题,而这个通讯的过程会导致乱序访问的问题,也是运行时的内存乱序的问题。

    现在的cpu架构中已经提供了内存屏障的功能,在x86的cpu中,实现了相应的内存屏障。

    写屏障(store barrier)、读屏障(load barrier) 和全屏障(full barrier)主要作用是:

    1、防止指令重排序;

    2、保证数据的可见性;

    注意:

    对于复合的操作,例如i++这种,volatile不能保证原子性。

  • 相关阅读:
    【信仰充值中心】Pale Moon 29 正式版更新日志
    【亚伦博客】我们勇敢的审查者
    狐吧吧刊——遇见最好的浏览器(16期)
    【信仰充值中心】Firefox 97 后续更新一览
    JavaTPoint Web 中文教程【翻译完成】
    声纹识别
    设计模式
    WSL迁移安装目录
    openvSwitch概述
    进程间通信几种方式
  • 原文地址:https://www.cnblogs.com/hujinshui/p/9572468.html
Copyright © 2020-2023  润新知