在多线程并发编程中synchronize一直是元老级别的存在,很多人称呼为重量级锁,但是随着JDK1.6之后,有些情况下就不显得那么重了。
一、对象锁(方法锁)实例与分析
类中非静态方法上的锁;用this做锁;
二、类锁实例与分析
类中静态方法上的锁;用XXX.class做锁;
三、引用对象作为锁,代码块实例与分析
用类中的成员变量引用做锁;
当一个线程试图访问同步代码块的时,它受限必须得到锁,退出或者抛出异常的时候必须释放锁。那么锁到底存在哪里呢?锁里面有哪些信息呢?
从JVM规范中可以看到Synchronized在JVM里面的实现原理,JVM基于进入和退出Monitor对象来实现同步和代码块的同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法的同步同样可以使用这两个指令来实现的,细节JVM规范里面没有详细说明,但是方法的同步同样可以使用这两个指令实现。
monitorenter指令实在变异后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象对应的monitor的所有权,即尝试获取对象的锁。
偏向锁的撤销
偏向 锁 使用了一种等到 竞 争出 现 才 释 放 锁 的机制,所以当其他 线 程 尝试竞 争偏向 锁时 ,持有偏向 锁 的 线 程才会 释 放 锁 。偏向 锁 的撤 销 ,需要等待全局安全点(在 这 个 时间 点上没有正在 执 行的字 节码 )。它会首先 暂 停 拥 有偏向 锁 的 线 程,然后 检查 持有偏向 锁 的 线 程是否活着,如果 线 程不 处 于活 动 状 态 , 则 将 对 象 头设 置成无 锁 状 态 ;如果 线 程仍然活着, 拥 有偏向 锁 的 栈会被 执 行,遍 历 偏向 对 象的 锁记录 , 栈 中的 锁记录 和 对 象 头 的 Mark Word 要么重新偏向于其他线 程,要么恢复到无 锁 或者 标记对 象不适合作 为 偏向 锁 ,最后 唤 醒 暂 停的 线 程。 图 2-1 中的 线程 1 演示了偏向 锁 初始化的流程, 线 程 2 演示了偏向 锁 撤 销 的流程。
偏向 锁 在 Java 6 和 Java 7 里是默 认 启用的,但是它在 应 用程序启 动 几秒 钟 之后才激活,如有必要可以使用 JVM 参数来关 闭 延 迟 : -XX:BiasedLockingStartupDelay=0 。如果你确定 应 用程序里所有的 锁 通常情况下 处 于 竞 争状 态 ,可以通 过 JVM 参数关 闭 偏向 锁 : -XX:-UseBiasedLocking=false ,那么程序默 认 会 进 入 轻 量 级锁 状 态 。
( 1 ) 轻 量 级锁 加 锁线 程在 执 行同步 块 之前, JVM 会先在当前 线 程的 栈桢 中 创 建用于存 储锁记录 的空 间 ,并将 对 象 头 中的 Mark Word 复制到 锁记录 中,官方称 为 Displaced Mark Word 。然后 线 程 尝试 使用CAS 将 对 象 头 中的 Mark Word 替 换为 指向 锁记录 的指 针 。如果成功,当前 线 程 获 得 锁 ,如果失败 ,表示其他 线 程 竞 争 锁 ,当前 线 程便 尝试 使用自旋来 获 取 锁 。( 2 ) 轻 量 级锁 解 锁轻 量 级 解 锁时 ,会使用原子的 CAS 操作将 Displaced Mark Word 替 换 回到 对 象 头 ,如果成功, 则 表示没有 竞 争 发 生。如果失 败 ,表示当前 锁 存在 竞 争, 锁 就会膨 胀 成重量 级锁 。 图 2-2 是两个 线 程同 时 争 夺锁 , 导 致 锁 膨 胀 的流程 图 。