javap命令生成的字节码中包含 ** monitorenter ** 和 ** monitorexit **指令
synchronized关键字基于上述两个指令实现了锁的获取和释放过程,解释器执行monitorenter时会进入到InterpreterRuntime.cpp
的InterpreterRuntime::monitorenter
函数
JavaThread* thread
指向java中的当前线程BasicObjectLock
类型的elem对象包含一个BasicLock
类型 _lock 对象和一个指向Object
对象的指针 _objclass BasicObjectLock { BasicLock _lock; // object holds the lock; oop _obj; }
BasicLock
类型 _lock 对象主要用来保存 _obj 所指向的Object对象的对象头数据
class BasicLock { volatile markOop _displaced_header;}
UseBiasedLocking标识虚拟机能否开启偏向锁功能,假如开启则执行fast_enter逻辑,否则执行slow_enter
在没有多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径
轻量级锁的获取及释放依赖屡次CAS指令,而偏向锁只依赖一次CAS原子指令置换ThreadID
,不过一旦出现多个线程竞争时必需撤销偏向锁,所以撤销偏向锁消耗的性能必需小于之前节省下来的CAS原子操作的性能消耗,不然得不偿失
JDK 1.6中默认开启偏向锁,可以通过-XX:-UseBiasedLocking
来禁用偏向锁
在HotSpot中,偏向锁的入口位于synchronizer.cpp
文件的ObjectSynchronizer::fast_enter
函数:
由BiasedLocking::revoke_and_rebias
方法实现,逻辑如下:
1、通过markOop mark = obj->mark()
获取对象的markOop数据mark,即对象头的Mark Word
2、判断mark能否为可偏向状态,即mark的偏向锁标志位为 1,锁标志位为 01
3、判断mark中JavaThread的状态:假如为空,则进入步骤(4);假如指向当前线程,则执行同步代码块;假如指向其它线程,进入步骤(5);
4、通过CAS原子指令设置mark中JavaThread为当前线程ID,假如执行CAS成功,则执行同步代码块,否则进入步骤(5);
5、假如执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),取得偏向锁的线程被挂起,撤销偏向锁,并更新为轻量级,更新完成后被阻塞在安全点的线程继续执行同步代码块;
只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由BiasedLocking::revoke_at_safepoint
方法实现:
偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用-XX:BiasedLockingStartupDelay=0
参数关闭推迟,假如确定应用程序中所有锁通常情况下处于竞争状态,可以通过XX:-UseBiasedLocking=false
参数关闭偏向锁。
在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是假如多个线程在同一时刻进入临界区,会导致轻量级锁膨胀更新重量级锁,所以轻量级锁的出现并非是要替代重量级锁
当关闭偏向锁功能,或者多个线程竞争偏向锁导致偏向锁更新为轻量级锁,会尝试获取轻量级锁,其入口位于ObjectSynchronizer::slow_enter
markOop mark = obj->mark()
方法获取对象的markOop数据mark;mark->is_neutral()
方法判断mark能否为无锁状态:mark的偏向锁标志位为 0,锁标志位为 01;假设线程A和B同时执行到临界区if (mark->is_neutral())
:
1、线程AB都把Mark Word复制到各自的_displaced_header字段,该数据保存在线程的栈帧上,是线程私有的;
2、Atomic::cmpxchg_ptr
原子操作保证只有一个线程可以把指向栈帧的指针复制到Mark Word,假设此时线程A执行成功,并返回继续执行同步代码块;
3、线程B执行失败,退出临界区,通过ObjectSynchronizer::inflate
方法开始膨胀锁;
轻量级锁的释放通过ObjectSynchronizer::fast_exit
完成。
重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从客户态到内核态的切换,切换成本非常高。
锁的膨胀过程通过ObjectSynchronizer::inflate
函数实现
synchronized.cpp
,大概实现过程如下:mark->has_monitor()
方法判断当前能否为重量级锁,即Mark Word的锁标识位为 10,假如当前状态为重量级锁,执行步骤(3),否则执行步骤(4);mark->monitor()
方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成;当锁膨胀完成并返回对应的monitor时,并不表示该线程竞争到了锁,真正的锁竞争发生在ObjectMonitor::enter
方法中。
1、通过CAS尝试把monitor的_owner字段设置为当前线程;
2、假如设置之前的_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行_recursions ++ ,记录重入的次数;
3、假如之前的_owner指向的地址在当前线程中,这种形容有点拗口,换一种说法:之前_owner指向的BasicLock在当前线程栈上,说明当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程,该线程成功取得锁并返回;
4、假如获取锁失败,则等待锁的释放;
monitor竞争失败的线程,通过自旋执行ObjectMonitor::EnterI
方法等待锁的释放,EnterI方法的部分逻辑实现如下:
1、当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ;
2、在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中;
3、node节点push到_cxq列表之后,通过自旋尝试获取锁,假如还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒,实现如下:
4、当该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock
尝试获取锁,TryLock方法实现如下:
其本质就是通过CAS设置monitor的_owner字段为当前线程,假如CAS成功,则表示该线程获取了锁,跳出自旋操作,执行同步代码,否则继续被挂起;
当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor::exit
方法中。
1、假如是重量级锁的释放,monitor中的_owner指向当前线程,即THREAD == _owner;
2、根据不同的策略(由QMode指定),从cxq或者EntryList中获取头节点,通过ObjectMonitor::ExitEpilog
方法唤醒该节点封装的线程,唤醒操作最终由unpark完成,实现如下:
3、被唤醒的线程,继续执行monitor的竞争;