锁的状态存放于对象头中,有4中锁状态, 无锁, 偏向锁, 轻量级锁, 重量级锁.
锁的状态也是依次升级且一旦升级就不会降级,
偏向锁:
从jdk.6加入对锁的优化手段, 偏向锁的场景为锁不存在竞争, 一个时刻总是仅有一个线程持有. 如果锁不存在竞争那么CAS的操作也是多余的.
偏向锁的核心思想是当一个线程获取了锁, 那么这个锁就进入了偏向状态, 这里的"偏"就是偏心于一个线程的意思.
偏向锁的获得和撤销流程
获取锁步骤:
1)判断锁对象是否是偏向锁(即锁标志位为01,偏向锁位为1),若为偏向锁状态执行2)。
2)判断锁对象的线程ID是否为当前线程的ID,如果是则说明已经获取到锁,执行代码块;否则执行3)。
3)当前线程使用CAS更新锁对象的线程ID为当前线程ID。如果成功,获取到锁;否则执行4)
4)当到达全局安全点,当前线程根据Mark Word中的线程ID通知持有锁的线程挂起,将锁对象Mark Word中的锁对象指针指向当前堆栈中最近的一个锁记录,偏向锁升级为轻量级锁,恢复被挂起的线程。
释放锁步骤:
偏向锁采用一种等到竞争出现时才释放锁的机制。当其他线程尝试竞争偏向锁时,当前线程才会释放释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点。
准确来说, 应该是撤销偏向锁或者重偏向
1)首先暂停持有偏向锁的线程。
2)撤销偏向锁,恢复到无锁状态或轻量级锁状态。
偏向锁的撤销流程图:
偏向锁: 状态
匿名偏向: 即初始状态,Mark Word中Thrad ID为null, 将当前线程id用CAS操作写入Mark Word中
重偏向: Mark Word里的epoch与 该对象类型klassOop中的epoch不一致时, 可以被重偏向, 将当前线程id用CAS操作写入Mark Word中
已偏向: Mark Word中Thrad ID有值, 当前线程ID比较, 一致时知己获取, 不一致时会膨胀为轻量级锁
偏向锁: 重偏向(Bulk Rebias)机制
场景为多个线程之前不存在竞争关系, 交替使用该锁, 因为偏向锁在执行完同步代码块时是没有释放锁的, 重偏向就是针对于多线程交替使用该锁的优化.
前置知识: 当一个对象实例化时, 会根据klassOop为原型进行实例化对象, 其中也包括了Mark Word部分. epoch就在Mark Word中
引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性
除了对象中的 epoch, 对象所属的类 klass 信息中,也会保存一个 epoch 值
每当遇到一个全局安全点时, 挂起所有线程,给KlassOop.epoch + 1
扫描所有持有KlassOop的实例对象的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将正在被锁定的对象中的epoch+1。
退出安全点后, 恢复暂停线程, 当有线程需要尝试获取偏向锁时, 直接检查KlassOop.epoch值是否与目标实例对象中存储的 epoch 值相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。
如:
t1获取锁,KlassOop.epoch=1,lock实例.epoch=1,执行完同步代码,
t2获取锁, 给KlassOop.epoch + 1=2, 扫描到t1栈发现没有锁定, 实例不做+1操作
检查KlassOop.epoch(2)==lock实例.epoch(1), 不相等则可以重偏量
如果相等, 说明t1的同步代码还没执行完, 会暂停t1线程, 将t1的锁升级为轻量级锁, 再恢复t1线程.
偏向锁: 升级为轻量级锁
存在超过一个线程竞争某一个对象时, 会发生偏向锁的撤销操作。 有趣的是, 偏向锁撤销后, 对象可能处于两种状态。
一种是不可偏向的无锁状态,(之所以不允许偏向, 是因为已经检测到了多于一个线程的竞争, 升级到了轻量级锁的机制)
hash code | age| 0 | 01
另一种是不可偏向的已锁 ( 轻量级锁) 状态
pointer to lock record | 00
之所以会出现上述两种状态, 是因为偏向锁不存在解锁的操作, 只有撤销操作。 触发撤销操作时:
-
原来已经获取了偏向锁的线程可能已经执行完了同步代码块, 使得对象处于 “闲置状态”,相当于原有的偏向锁已经过期无效了。此时该对象就应该被直接转换为不可偏向的无锁状态。
-
原来已经获取了偏向锁的线程也可能尚未执行完同步代码块, 偏向锁依旧有效, 此时对象就应该被转换为被轻量级加锁的状态
轻量级锁:
jdk1.6加入的锁优化手段, 为了优化重量级锁从用户状态与系统内核状态转换互斥的性能消耗.
轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
获取锁步骤:
1)判断是否处于无锁状态,若是,则JVM在当前线程的栈帧中创建锁记录(Lock Record)空间,用于存放锁对象中的Mark Word的拷贝,官方称为Displaced Mark Word;否则执行步骤3)。
2)当前线程尝试利用CAS将锁对象的Mark Word更新为指向锁记录的指针。如果更新成功意味着获取到锁,将锁标志位置为00,执行同步代码块;如果更新失败,执行步骤3)。
3)判断锁对象的Mark Word是否指向当前线程的栈帧,若是说明当前线程已经获取了锁,执行同步代码,否则说明其他线程已经获取了该锁对象,执行步骤4)。
4)当前线程尝试使用自旋来获取锁,自旋期间会不断的执行步骤1),直到获取到锁或自旋结束。因为自旋锁会消耗CPU,所以不能无限的自旋。如果自旋期间获取到锁(其他线程释放锁),执行同步块;否则锁膨胀为重量级锁,当前线程阻塞,等待持有锁的线程释放锁时的唤醒。
释放锁步骤:
1)从当前线程的栈帧中取出Displaced Mark Word存储的锁记录的内容。
2)当前线程尝试使用CAS将锁记录内容更新到锁对象中的Mark Word中。如果更新成功,则释放锁成功,将锁标志位置为01无锁状态;否则,执行3)。
3)CAS更新失败,说明有其他线程尝试获取锁。需要释放锁并同时唤醒等待的线程。
重量级锁:
重量级锁在Synchronized的原理章节单独说明