美文网首页
Java并发(二):底层实现原理

Java并发(二):底层实现原理

作者: Jorvi | 来源:发表于2019-02-20 10:41 被阅读0次

    一. volatile

    volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”

    实现原理

    volatile修饰的共享变量,在进行写操作的时候,会多出一条lock前缀的汇编指令lock addl $0x0,(%esp)

    lock前缀的汇编指令的作用:

    1. 将当前处理器缓存回写到内存。
    2. 上述回写操作会导致其他处理器的缓存失效。

    正是这两条特性保证了volatile共享变量的“可见性”。

    二. synchronized

    Java中的每一个对象都可以作为锁。

    • 对于普通同步方法,锁的是当前实例对象(this)
    • 对于静态同步方法,锁的是当前类的Class对象(java.lang.Class)
    • 对于同步方法块,锁的是synchronized括号里配置的对象(实例对象 / Class对象)

    实现原理

    synchronized是JVM层面的同步方法

    在JVM里,使用monitorentermonitorexit指令实现synchronized同步。

    任何对象都有一个monitor与之关联,在编译时,会在同步代码开始的位置加入monitorenter指令,在同步代码结束处和异常处加入monitorexit指令。

    当线程执行到monitorenter指令时,将尝试获取要锁住的对象的monitor所有权,如果成功则锁住该对象。

    1. Java对象头

    synchronized用的锁保存在Java对象头里。

    • 对象头结构
    长度(32/64位JVM) 内容 说明
    32/64bit Mark Word 存储对象的hashCode或锁信息等
    32/64bit Class Metadata 存储对象类型数据的指针
    32/32bit Array length 数组的长度(当前对象是数组才有)
    • 32位JVM的Mark Word的默认存储结构
    锁状态 25bit 4bit 1bit (是否为偏向锁) 2bit (锁标志位)
    无锁 对象的hashCode 对象分代年龄 0 01
    • Mark Word随锁标志位的变化而变化
    2. 锁的升级与对比

    为了减少获得锁和释放锁带来的性能消耗,Java SE 1.6引入了偏向锁和轻量级锁。

    锁的级别:无锁 -- 偏向锁 -- 轻量级锁 -- 重量级锁。

    • 偏向锁

    经研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。

    当一个线程访问同步块并获取锁时,会在对象头和栈帧的锁记录里保存锁偏向的线程ID,以后该线程进入退出同步块时无需通过CAS操作来加锁解锁,只需要测试一下对象头的Mark Word里是否保存了当前线程的偏向锁。

    只有当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。此时会执行偏向锁的撤销操作。

    • 轻量级锁

    线程执行同步块前,JVM先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中(Displaced Mark Word),然后线程尝试用CAS将对象头中的Mark Word替换为指向锁记录的指针,如果成功,当前线程获得锁;如果失败,则当前线程尝试自旋来获取锁。

    轻量级锁解锁时,线程会尝试用CAS将Displaced Mark Word替换回对象头中,如果成功,则解锁成功;如果失败,则表示锁存在竞争,膨胀为重量级锁。

    优点 缺点 适用场景
    偏向锁 加锁和解锁不需要额外的消耗 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块的场景
    轻量级锁 竞争的线程不会阻塞,提高程序响应速度 如果始终得不到锁,自旋会消耗CPU 追求响应速度,同步块执行速度非常快
    重量级锁 线程竞争不适用自旋,不会消耗CPU 线程阻塞,响应时间慢 追求吞吐量,同步块执行速度慢

    三. 原子操作

    原子操作是指“不可被中断的一个或一系列操作”。

    1. 处理器实现原子操作

    处理器会自动保证基本的内存操作的原子性,处理器保证从系统内存中读取或写入一个字节是原子的。

    对于复杂的内存操作,处理器提供总线锁定缓存锁定来保证复杂内存操作的原子性。

    总线锁定

    使用处理器提供的一个LOCK # 信号,当一个处理器在总线上传输此信号时,其他处理器的请求将被阻塞,那么当前处理器就可以独占共享内存了。

    缓存锁定

    内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,会使其他处理器的缓存行失效。

    2. Java实现原子操作

    Java中通过锁和循环CAS来实现原子操作。

    相关文章

      网友评论

          本文标题:Java并发(二):底层实现原理

          本文链接:https://www.haomeiwen.com/subject/fzdwhqtx.html