• java并发编程的艺术


    术语 描述
    memory barries(内存屏障) 处理器指定,实现对内存操作的顺序限定
    cache line(缓冲行) 缓存中的最小存储单位,需要使用多个主内存周期(存储周期?)
    atomic operations(原子操作) 不可中断的操作
    cache line fill (缓存行填充) 处理器读取整个缓存行到适当的缓存
    cache hit(缓存命中) 处理器操作地址是缓存行填充的内存地址
    write hit(写命中) 写回数据时,如果内存地址在缓存行中,写命中(直接操作缓存行)。

    volatile

    • volatile提供了比synchronized的使用和成本低,不会引起上下文切换和调用
    • (原理)转汇编时会多出一行lock汇编代码
      • 将当前缓存行写回内存系统
      • 写回操作使其他cpu的缓存无效

    ps: 缓存一致性协议,每个处理器通过嗅探在总线上传播的数据检查缓存是否过期。发现缓存行的内存被修改后,将缓存行置为无效状态,当处理器对数据修改操作时,把数据从内存读取到处理器缓存。

    Lock的处理器原理

    缓存回写内存

    锁缓存、写回内存、使用缓存一致性确保修改原子性。缓存一致性会组织同时修改由两个以上处理器缓存的内存区域数据。

    一个处理器的缓存回写内存会导致其他处理器的相同缓存失效

    处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存在总线上保持一致。

    volatile使用优化

    • 追加字节填满缓冲行(Linked-TransferQueue),大多处理器告诉缓存行是64字节。试图修改缓存行数据时,会锁定缓存行。导致其他处理器不能访问锁定的节点。
    • 什么时候不能追加到64字节
      • 缓存行为32字节的cpu时
      • 共享变量不会被频繁写入
        ps:java7会淘汰或重新排列无用字段,不能单独使用Object对象来填充

    synchronized

    • 曾经很重,1.6优化(引入偏向锁和轻量级锁)后,变轻。
    • 对于普通方法,锁是当前实例对象
    • 静态同步方法,锁是当前类的class对象
    • 对于同步方法块(synchonized)

    在哪里上锁

    synchronized的锁是存在java对象头里面。

    ps:对象头:数组类型用三个字宽(多出的一个存放array length);非数组类型,用两个字宽(字宽:Word,32位机1字宽为4字节)

    q1:64位虚拟机,在轻量级锁、重量级锁时,mark word的结构?

    对象头的mark word

    Mark Word默认存储对象的HashCode、分代年龄、锁标记位。但是会随着锁标志位的变化而变化。

    锁升级与对比

    1.6为了减少获得锁和释放锁的 带来的性能消耗,引入了偏向锁和轻量级锁。因此共有四种锁状态(如下),同时锁只能升级,无法降级。

    • 无锁状态
    • 偏向锁状态
    • 轻量级锁状态
    • 重量级锁状态

    偏向锁

    • 偏向锁撤销:
        1. 持有锁的线程不会在同步代码块执行结束后,释放锁。
        2. 新线程竞争锁-->暂停持有偏向锁的线程-->检查状态
                --live-->恢复无锁、升级偏向锁--  恢复持有 偏向锁的线程
                --dead-->置为无锁          --/
    
    • 关闭偏向锁:
    启动几秒后才会激活,关闭延迟:-XX:BiasedLockingStartupDelay=0
    如果锁通常处于竞争状态时,可以关闭偏向锁:-XX:-UseBiasedLocking=false
    

    轻量级锁

    • 加锁
        1.  将对象头中的mark word复制到锁记录(当前线程的栈帧)中。
        2. CAS将mark word替换为指向锁记录的指针
        3. 成功则获取锁,失败则尝试自旋获取锁
    
    • 解锁
       1. CAS将Displaced Mark Word替换回到对象头。
       2. 成功则表示无竞争发生;失败则表示存在竞争,膨胀为重量锁。
    

    重量级锁

    重量级锁会阻塞获取锁的线程,当持有锁的线程被释放后,才会唤醒这些线程。

    原子操作实现原理

    cpu的实现

    总线锁定

    • 使用处理器提供的LOCK #信号
    • 一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞住,该处理器可以独占共享内存
      ps:总线锁,锁定的是CPU和内存间的通信。

    缓存锁

    • 不用声明LOCK #信号
    • 使用缓存一致性机制,来保证操作的原子性

    两种情况处理器不会使用缓存锁定

    • 数据不能被缓存在处理器内部,操作的数据跨多个缓存行时。使用总线锁定
    • 某些处理器不支持缓存锁定。

    ps:针对以上机制,通过Intel处理器提供的Lock前缀的指令来实现(位测试和修改指令:BTS、BTR、BTC;交换指令:XADD、CMPXCHG;其他操作指令:ADD、OR)

    java的实现

    思路:锁和循环CAS

    CAS
    • CAS利用了处理器提供的CMPXCHG指令实现的。
    • 一些问题(ABA、循环时间长开销大、只能保证一个共享变量的操作)
    使用锁机制实现原子操作

    ps:除了偏向锁,JVM实现锁的方式都采用了循环CAS。

  • 相关阅读:
    a标签跳转新页面
    js或jquery实现页面打印(局部打印)
    js实现点击定位最顶端
    js实现pdf对页面的打印
    js动态创建input
    sha1加密算法
    JsonHelper修改4.0
    List<T>做数据源绑定的问题
    VS2013智能提示
    Newtonsoft.Json(Json.Net)学习笔记
  • 原文地址:https://www.cnblogs.com/enhe/p/12141680.html
Copyright © 2020-2023  润新知