偏向锁基本概念

synchronized底层是调用os函数互斥量 pthread_mutex_lock来实现的,一旦调用了os函数,那么线程就会进入内核态,需要进行CPU上下文切换,代价很大。
在JDK1.6中为了提高一个对象在一段很长的时间内都只被一个线程用做锁对象场景下的性能,引入了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只会执行几个简单的命令,而不是开销相对较大的CAS命令。我们来看看偏向锁是如何做的。

预备知识

CAS操作 = 内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
对象头详解:
语雀内容

偏向锁加锁过程

先看JVM中偏向锁的源码

  1. CASE(_monitorenter): {
  2. //获取到当前锁对象
  3. oop lockee = STACK_OBJECT(-1);
  4. CHECK_NULL(lockee);
  5. //这里的意思就是获取一个空的 lock record
  6. BasicObjectLock* limit = istate->monitor_base();
  7. BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  8. BasicObjectLock* entry = NULL;
  9. while (most_recent != limit ) {
  10. if (most_recent->obj() == NULL) entry = most_recent;
  11. else if (most_recent->obj() == lockee) break;
  12. most_recent++;
  13. }
  14. //偏向锁逻辑,这里的可以看做是lock redord
  15. if (entry != NULL) {
  16. //将lock record的obj_ref 指向 锁对象
  17. entry->set_obj(lockee);
  18. //给定一个默认的加锁状态,为false
  19. int success = false;
  20. uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
  21. //markOop 指的是对象头中的mark word
  22. markOop mark = lockee->mark();
  23. intptr_t hash = (intptr_t) markOopDesc::no_hash;
  24. // implies UseBiasedLocking
  25. // 如果锁对象的mark word 是偏向模式,也就是低三位为 101
  26. if (mark->has_bias_pattern()) {
  27. uintptr_t thread_ident;
  28. uintptr_t anticipated_bias_locking_value;
  29. thread_ident = (uintptr_t)istate->thread();
  30. anticipated_bias_locking_value =
  31. (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
  32. ~((uintptr_t) markOopDesc::age_mask_in_place);
  33. //如果偏向的线程id是自己,且epoch等于对象头的epoch
  34. if (anticipated_bias_locking_value == 0) {
  35. // already biased towards this thread, nothing to do
  36. if (PrintBiasedLockingStatistics) {
  37. (* BiasedLocking::biased_lock_entry_count_addr())++;
  38. }
  39. success = true;
  40. }
  41. //如果JVM 关闭了偏向锁,则尝试偏向撤销
  42. else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
  43. // try revoke bias
  44. markOop header = lockee->klass()->prototype_header();
  45. if (hash != markOopDesc::no_hash) {
  46. header = header->copy_set_hash(hash);
  47. }
  48. if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
  49. if (PrintBiasedLockingStatistics)
  50. (*BiasedLocking::revoked_lock_entry_count_addr())++;
  51. }
  52. }
  53. //如果epoch不等于对象头中的epoch,则尝试重偏向
  54. else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
  55. // 构建一个偏向当前线程的mark word
  56. markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
  57. if (hash != markOopDesc::no_hash) {
  58. new_header = new_header->copy_set_hash(hash);
  59. }
  60. //cas 将偏向当前线程的mark word替换到象头中的mark word,参考注释:explainCAS
  61. if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
  62. if (PrintBiasedLockingStatistics)
  63. (* BiasedLocking::rebiased_lock_entry_count_addr())++;
  64. }
  65. //CAS失败则证明有线程在竞争资源,升级锁
  66. else {
  67. CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
  68. }
  69. success = true;
  70. }
  71. //走到这里说明要么是匿名偏向(没有任何线程偏向),要么是偏向别的线程
  72. else {
  73. //下面代码的意思,就是判断是否是匿名偏向,如果是则加锁成功,如果不是则升级为轻量锁
  74. // try to bias towards thread in case object is anonymously biased
  75. //创建一个匿名偏向的markword 叫 header
  76. markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
  77. (uintptr_t)markOopDesc::age_mask_in_place |
  78. epoch_mask_in_place));
  79. if (hash != markOopDesc::no_hash) {
  80. header = header->copy_set_hash(hash);
  81. }
  82. //创建一个偏向当前线程的markword叫new_header
  83. markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
  84. // debugging hint
  85. DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
  86. //CAS 判断对象头的mark word 和匿名偏向是否一致,一致则替换为当前线程的markword
  87. if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
  88. if (PrintBiasedLockingStatistics)
  89. (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
  90. }
  91. //CAS升级到轻量锁
  92. else {
  93. CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
  94. }
  95. success = true;
  96. }
  97. }
  98. //如果success = false;其实就是最上面定义的默认值,仔细读上面的代码,会发现只有两种情况
  99. // 1. 表达式 mark->has_bias_pattern() = false 代表的是 对象已经不是可偏向的了
  100. // 2. 表达式 (anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0 成立,代表的是,JVM关闭了偏向锁
  101. // 总的来说,就是JVM关闭了偏向锁功能或者对象不是可偏向的,会走到这里
  102. if (!success) {
  103. //轻量锁逻辑,在轻量锁中解释
  104. markOop displaced = lockee->mark()->set_unlocked();
  105. entry->lock()->set_displaced_header(displaced);
  106. bool call_vm = UseHeavyMonitors;
  107. if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
  108. // Is it simple recursive case?
  109. if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
  110. entry->lock()->set_displaced_header(NULL);
  111. } else {
  112. CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
  113. }
  114. }
  115. }
  116. UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
  117. } else {
  118. istate->set_msg(more_monitors);
  119. UPDATE_PC_AND_RETURN(0); // Re-execute
  120. }
  121. }

整体流程图(大概)
image.png

  • 只有一个线程在加锁的时候,分为两种一种是第一次加锁,一种是重入锁
  • 第一次加锁的情况下,对象头中为默认值,也就是低位为00000101

image.png

  • 重入锁,一个线程sync 套用 sync或者是同一个线程第二次加锁,走的流程

image.png

是否过期是根据对象头内保存的ep时间戳去判断的。

这里要注意,由于有重入的判断,所以其实一个偏向锁在释放了,为了判断重入,会将线程id一直保留在对象头中,不会因为锁的释放而恢复成0值(据说极为苛刻的情况可以恢复)

重偏向流程图:
image.png
如果epoch已过期,则需要重偏向,利用CAS指令将锁对象的mark word替换为一个偏向当前线程且epoch为类的epoch的新的mark word
重偏向也会发生竞争关系,例如 synchronized 中套用了一个synchronize。那么就发生了同一个线程的锁的竞争,那么会直接升级为重量锁

注释

  • explainCAS:

CAS操作,表达式Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark)
CAS原理 = 内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做,此过程是原子性的,不可被打断
内存值V = mark为上面取出的对象头的mark word参考22行
旧的预期值A = lockee->mark_addr() 为当前对象头中mark word
新的值B = new_header 在代码上下文中相当于偏向当前线程的id+epoch。
如果上面取出的mark和现在对象头中的一样,则证明对象头没有被人改过,就像对象头换为偏向当前线程的mark word;不一样,则证明有人改了对象头,CAS就失败了

  • lock record

lock record 是JVM中C++代码在当前加锁线程执行synchronized的时候,在线程栈中创建的一个对象,这个对象非常重要,贯穿了整个synchronized的加锁过程。大概的结构是这样的
image.png
其中 displaced 默认是为空的,obj ref 存的是指向锁对象所在内存地址的指针