• Java多线程系列之:内存可见性


    一, 什么是可见性?

    1,可见性:一个线程对共享变量值的修改,能够及时的被其他线程看到。

    2,什么是共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量

    二,Java内存模型(JMM)

    1,什么是Java内存模型?

      它描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量的底层细节

    2,内存中如何存储变量?

      所有的变量都存储在主内存中。

      每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(它是主内存中该变量的一份拷贝),如图:

      

    3,两条规定

      线程对共享变量的所有操作必须在自己的工作内存中进行,不能直接从主内存中读写

      不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成

    4,共享变量可见性实现的原理(可见性是如何实现的?)

      线程修改后的共享变量值能够及时从工作内存刷新到主内存中。其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中。

    三,synchronized是如何实现可见性的?

    1,JMM对于synchronized有两条规定:

      线程解锁前,必须把共享变量的最新值刷新到主内存

      线程加锁时,将清空工作内存中共享变量的值,这样在使用共享变量时就需要从主内存中重新获取最新的值

    2,线程执行互斥代码(被synchronized修饰的代码)的过程:

      获得互斥锁

      清空工作内存

      从主内存拷贝变量的最新副本到自己工作内存

      执行代码

      将更改后的共享变量的值刷新到主内存中

      释放互斥锁

    四,volatile如何实现可见性?

    1,简单的说:

      volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存,这样不同的线程总能看到该变量的最新值

    2,深入来讲:

      volatile是通过加入内存屏障和禁止重排序优化来实现。

      对volatile变量执行写操作时,会在写操作后加入一条sotre屏障指令,强制写入主内存中

      对volatile变量执行读操作事,会在读操作前加入一条load屏障指令,强制清空缓冲区数据,需要时从主内存中拿,并禁止指令重排序

    2,线程写volatile变量的过程:

      改变线程工作内存中volatile变量副本的值

      将改变后的副本的值从工作内存中刷新到主内存

    3,线程读volatile变量的过程

      从主内存中读取volatile变量的最新值到线程的工作内存中

      从工作内存读取volatile变量的副本

    4,volatile不保证变量复合操作的原子性

    比如: 

      private int number = 0;

      number ++;//注意:这一步不是原子操作,加 volatile也无法保证原子性

    因为 number++ 在计算机指令级操作要分三步才能完成:

      读取number的值

      将number的值加1

      写入最新的number的值

    5,如何保证number自增操作的原子性?

      使用synchronized关键字

      使用ReentrantLock

      使用AtomicInteger

    五,重排序

    定义:代码书写的顺序和实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化

    分类:

      编译器优化的重排序(编译器优化)

      指令级并行重排序(处理器优化)

      内存系统的重排序(处理器优化)

    2,举例分析:

      int num1 = 1;

      int num2 = 2;

      int sum = num1 + num2;

    在单线程下:1,2,顺序可以重排,但第三行不行。所以重排序不会给线程带来内存可见性问题。(因为在单线程下,Java编译器和处理器会保证:无论如何重排序,程序执行的结果和代码顺序执行的结果一致)

    在多线程下:程序交替执行,重排序可能导致内存可见性问题

    六,可见性分析:

    导致共享变量在线程间不可见的原因:

    程序的交叉执行

    重排序结合线程交叉执行

    共享变量更新后的值没有在工作内存和主内存之间及时更新

    七,synchronized为什么可以实现线程安全?

    代码块具有原子性,线程不能交叉执行

    给锁住的代码块是单线程执行的,重排序结果不变

    synchronized的两条规定可以保证变量在工作内存和主内存之间及时更新

    八,volatile和sychronized比较

    volatile不需要加锁,比synchronized更轻量级,不会阻塞线程,效率高

    从内存可见性角度:volatile读相当于加锁,写相当于解锁

    synchronized既能保证可见性,有能保证原子性。volatile只能保证可见性

    注意:final也可以保证内存可见性

  • 相关阅读:
    关于findViewById返回空指针的错误
    android客户端向服务器发送图片和文字,类似于发微博。能用json格式发送吗?
    nodejs 学习资料大全
    篇章三:[AngularJS] 使用AngularCSS動態載入CSS
    篇章二:[AngularJS] 使用AngularAMD動態載入Service
    篇章一:[AngularJS] 使用AngularAMD動態載入Controller
    Angular 资料大集合
    js-音乐播放器,播放|暂停|滑块的功能
    JS-以鼠标位置为中心的滑轮放大功能demo1
    使用 Electron 构建桌面应用(拖动控制篇)
  • 原文地址:https://www.cnblogs.com/inspred/p/11075631.html
Copyright © 2020-2023  润新知