zoukankan      html  css  js  c++  java
  • JAVA锁和volatile的内存语义&volatile的使用场景

    JAVA锁的内存语义

    当线程释放锁时,JMM(Java Memory Model)会把该线程对应的本地内存中的共享变量刷新到主内存中。

    当线程获取锁时,JMM会将该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

    对比锁释放-读取的内存语义与volatile写-读的内存语义可以看出,锁释放与volatile写具有相同的内存语义;锁获取与volatile读具有相同的内存语义。

    下面对锁释放和锁获取的内存语义做个总结。

    • 线程1释放一个锁,实质上是线程1向接下来将要获取这个锁的某个线程发出了(线程1对共享变量所做修改的)消息。
    • 线程2获取一个锁,实质上是线程2接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
    • 线程1锁释放,随后线程2获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

    锁内存语义的实现

    锁有很多种,但其基本原理都是差不多的。书上是以ReentrantLock中的公平锁与非公平锁作为案例分析,有兴趣的同学可以去阅读原籍和源码。现总结如下:

    • 公平锁和非公平锁释放时,最后都要写一个volatile变量state。
    • 公平锁获取是,首先会去读volatile变量。
    • 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和写的内存语义。

      所以锁释放-获取的内存语义的实现至少有下面两种方式:

      1)利用volatile变量的写-读所具有的内存语义。

      2)利用CAS所附带的volatile读和volatile写的内存语义。

      由此可知:并发包下的类的实现方式大部分都是基于这两种方式实现的。

    参考 https://www.cnblogs.com/yuanfy008/p/9346925.html

    1.volatile的内存语义与实现

    1.1 volatile读写的内存语义

    volatile读写的内存语义 与 锁获取/锁释放 的内存语义对应相同

    这两条保证了volatile修饰变量的可见性。 
    那JMM如何能够保证volatile实现其内存语义的,简单来说就是通过内存屏障。如果看过volatile变量汇编后的指令代码就会在代码中发现一句:

    lock add1 $0x0

    它的简单含义就是要把工作内存中的共享变量值刷新到主内存中,相当于加入内存屏障。

    1.2 volatile有序的内存语义

    volatile的另一个特性是禁止指令重排序,这里的内存语义我们可以总结为:

    volatile读之后 的操作不会被重排序到 volatile读之前
    volatile写之前 的操作不会被重排序到 volatile写之后
    先volatile写–后volatile读,不可重排序


    JMM通过插入内存屏障来实现以上语义,实质上有四种内存屏障策略:

    volatile写操作前插入StoreStore屏障
    volatile写操作后插入StoreLoad屏障
    volatile读操作前插入LoadLoad屏障
    volatile读操作后插入LoadStore屏障
    其中StoreLoad屏障是全能型屏障,可以完成其他3个屏障的功能。所以它被大部分CPU支持。不同CPU有着不同的重排序规则,但是这一套JMM屏障策略可以完成所有类型CPU下的volatile语义。例如对于x86的CPU,它本身只支持写-读操作的重排序,对读-读,读-写,写-写操作的重排序都不支持;那么我们只需要加入StoreLoad屏障来避免写-读操作的重排序即可实现volatile语义。

    亲测:volatile修饰的变量,对其相关操作是不具有原子性的

    eg:volatile修饰的共享变量a 的自增操作 a++,在多个线程中运行;最后a的值很有可能小于++的执行次数。(因为a++  实际是 a=a+1,是加法和赋值两个操作,不是原子操作。volatile不保证原子性,所以出现了小于++执行次数的结果)

            volatile修饰的共享变量 ArrayList<Object> list,添加元素的操作list.add(new Object()),在多个线程中运行;最后list.size()也很有可能小于add执行的次数。(原因同上,list.add也不是原子操作)

            对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。 

    volatile的不能完全取代synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件volatile修饰的变量才能保证在并发环境的线程安全:

      (1)对变量的写操作不依赖于当前值。  --待思考

      (2)该变量没有包含在具有其他变量的不变式中。 --待思考

    三种类型的指令重排序

    为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种:

      (1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

      (2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
      (3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

    参考 http://www.cnblogs.com/paddix/p/5374810.html

  • 相关阅读:
    搭建 Linux 下 GitLab 服务器(转)
    sql语法:inner join on, left join on, right join on具体用法
    Android Studio之同一应用创建多个Activity(一)
    java环境变量配置
    老鸟的Python新手教程
    域名注冊以及域名解析设置
    Android在WebView上构建Web应用程序
    利用JasperReport+iReport进行Web报表开发
    android App Widgets
    多数据库下activiti的流程定义缓存问题
  • 原文地址:https://www.cnblogs.com/genggeng/p/10030923.html
Copyright © 2011-2022 走看看