synchronize偏向锁底层实现原理

简介: 无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。

1 偏向锁的意义


无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。


偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。


在:


创建一个线程并在线程中执行循环监听的场景下


或单线程操作一个线程安全集合时


同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。


获取偏向锁的场景:


在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:


① : 先判断Mard Word的Thread ID是否有值


没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)

有,则表示当前资源被线程占用,需要判断该线程是不是自己

该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)

该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁

轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID。


一旦出现多个线程竞争时必须撤销偏向锁,所以:


撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗


不然得不偿失!


JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。


2 偏向锁的获取


偏向锁的入口,synchronizer.cpp 文件的


ObjectSynchronizer::fast_enter

6.png


由BiasedLocking::revoke_and_rebias实现

5.png


2.1 markOop mark = obj->mark()

获取对象的markOop数据mark,即对象头的Mark Word

4.png


2.2 判断mark是否为可偏向状态

mark的偏向锁的锁标志位为 01


2.3 判断mark中JavaThread的状态

若指向当前线程,则执行同步代码块

若为空,则走4

若指向其它线程,则走5

2.4 执行CAS原子指令

设置mark中JavaThread为当前线程ID。


若CAS成功,则执行同步代码块,否则走5。

2.5 执行CAS失败

说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。


升级完成后被阻塞在安全点的线程继续执行同步代码块。


BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {

 assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");


 // We can revoke the biases of anonymously-biased objects

 // efficiently enough that we should not cause these revocations to

 // update the heuristics because doing so may cause unwanted bulk

 // revocations (which are expensive) to occur.

 // step1

 markOop mark = obj->mark();

 if (mark->is_biased_anonymously() && !attempt_rebias) {

   // We are probably trying to revoke the bias of this object due to

   // an identity hash code computation. Try to revoke the bias

   // without a safepoint. This is possible if we can successfully

   // compare-and-exchange an unbiased header into the mark word of

   // the object, meaning that no other thread has raced to acquire

   // the bias of the object.

   markOop biased_value       = mark;

   markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());

   markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);

   if (res_mark == biased_value) {

     return BIAS_REVOKED;

   }

 } else if (mark->has_bias_pattern()) {

   Klass* k = obj->klass();

   markOop prototype_header = k->prototype_header();

   if (!prototype_header->has_bias_pattern()) {

     // This object has a stale bias from before the bulk revocation

     // for this data type occurred. It's pointless to update the

     // heuristics at this point so simply update the header with a

     // CAS. If we fail this race, the object's bias has been revoked

     // by another thread so we simply return and let the caller deal

     // with it.

     markOop biased_value       = mark;

     markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);

     assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");

     return BIAS_REVOKED;

   } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {

     // The epoch of this biasing has expired indicating that the

     // object is effectively unbiased. Depending on whether we need

     // to rebias or revoke the bias of this object we can do it

     // efficiently enough with a CAS that we shouldn't update the

     // heuristics. This is normally done in the assembly code but we

     // can reach this point due to various points in the runtime

     // needing to revoke biases.

     if (attempt_rebias) {

       assert(THREAD->is_Java_thread(), "");

       markOop biased_value       = mark;

       markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());

       markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);

       if (res_mark == biased_value) {

         return BIAS_REVOKED_AND_REBIASED;

       }

     } else {

       markOop biased_value       = mark;

       markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());

       markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);

       if (res_mark == biased_value) {

         return BIAS_REVOKED;

       }

     }

   }

 }

 HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);

 if (heuristics == HR_NOT_BIASED) {

   return NOT_BIASED;

 } else if (heuristics == HR_SINGLE_REVOKE) {

   Klass *k = obj->klass();

   markOop prototype_header = k->prototype_header();

   if (mark->biased_locker() == THREAD &&

       prototype_header->bias_epoch() == mark->bias_epoch()) {

     // A thread is trying to revoke the bias of an object biased

     // toward it, again likely due to an identity hash code

     // computation. We can again avoid a safepoint in this case

     // since we are only going to walk our own stack. There are no

     // races with revocations occurring in other threads because we

     // reach no safepoints in the revocation path.

     // Also check the epoch because even if threads match, another thread

     // can come in with a CAS to steal the bias of an object that has a

     // stale epoch.

     ResourceMark rm;

     if (TraceBiasedLocking) {

       tty->print_cr("Revoking bias by walking my own stack:");

     }

     EventBiasedLockSelfRevocation event;

     BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL);

     ((JavaThread*) THREAD)->set_cached_monitor_info(NULL);

     assert(cond == BIAS_REVOKED, "why not?");

     if (event.should_commit()) {

       event.set_lockClass(k);

       event.commit();

     }

     return cond;

   } else {

     EventBiasedLockRevocation event;

     VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);

     VMThread::execute(&revoke);

     if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) {

       event.set_lockClass(k);

       // Subtract 1 to match the id of events committed inside the safepoint

       event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);

       event.set_previousOwner(revoke.biased_locker());

       event.commit();

     }

     return revoke.status_code();

   }

 }

 assert((heuristics == HR_BULK_REVOKE) ||

        (heuristics == HR_BULK_REBIAS), "?");

 EventBiasedLockClassRevocation event;

 VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,

                               (heuristics == HR_BULK_REBIAS),

                               attempt_rebias);

 VMThread::execute(&bulk_revoke);

 if (event.should_commit()) {

   event.set_revokedClass(obj->klass());

   event.set_disableBiasing((heuristics != HR_BULK_REBIAS));

   // Subtract 1 to match the id of events committed inside the safepoint

   event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);

   event.commit();

 }

 return bulk_revoke.status_code();

}



3 偏向锁的撤销


只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。


偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:


void BiasedLocking::revoke_at_safepoint(Handle h_obj) {

 assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint");

 oop obj = h_obj();

 HeuristicsResult heuristics = update_heuristics(obj, false);

 if (heuristics == HR_SINGLE_REVOKE) {

   revoke_bias(obj, false, false, NULL, NULL);

 } else if ((heuristics == HR_BULK_REBIAS) ||

            (heuristics == HR_BULK_REVOKE)) {

   bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);

 }

 clean_up_cached_monitor_info();

}


偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)

暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态

撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态

偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:


-XX:BiasedLockingStartupDelay=0


若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:


XX:-UseBiasedLocking=false(默认打开)


偏向锁的释放

遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.


把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。


总结


当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作

多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率

偏向锁的重入计数依靠线程栈里Lock Record个数

偏向锁撤销失败,最终会升级为轻量级锁

偏向锁退出时并没有修改Mark Word,也就是没有释放锁

偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁

偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁

目录
相关文章
|
16天前
|
Java
无锁和偏向锁有什么区别吗
【10月更文挑战第20天】无锁和偏向锁有什么区别吗
15 0
|
3月前
|
Java
什么是 CAS(自旋锁)? 它的优缺点? 如何使用CAS实现一把锁?
该博客文章解释了什么是CAS(自旋锁),包括CAS的基本概念、实现原理、优缺点,以及如何使用CAS实现锁的逻辑,并提供了使用CAS实现锁的Java完整代码示例和测试结果。
什么是 CAS(自旋锁)? 它的优缺点? 如何使用CAS实现一把锁?
|
5月前
|
存储 安全 算法
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
134 1
|
安全 算法 Java
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
|
Java 程序员 API
【Lock锁的使用与原理】
【Lock锁的使用与原理】
177 0
|
6月前
|
安全 Java
大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的
字节跳动大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的
71 0
|
6月前
|
存储 安全 Java
Synchronized锁工作原理
Synchronized锁工作原理
|
6月前
|
存储 缓存 编译器
C++11及上的原子操作底层原理与锁实现
C++11及上的原子操作底层原理与锁实现
408 0
|
消息中间件 安全 Java
Lock 锁底层实现
Lock 锁底层实现
52 0
|
存储 Java 对象存储