在Java中,synchronized关键字既可修饰方法,又可修饰对象,在用于对象时既可修饰普通对象也可用于类对象。
synchronized关键字
用法
- 修饰方法:根据JVM规范2.11.10节,方法级同步是隐式的,运行时常量池method_info结构用ACC_SYNCHRONIZED标志区分synchronized方法,当调用ACC_SYNCHRONIZED修饰的方法时,执行线程会进入监视器,然后执行该方法,最后退出监视器。
- 修饰对象:用synchronized修饰对象时通常使用synchronized块语句,JVM提供了monitorenter指令和monitorexit指令支持这种特性。使用javap命令反编译字节码文件后会看到在synchronized块语句的入口和出口分别有monitorenter和monitorexit指令。
monitorenter指令
根据先前文章分析的模板解释器的初始化过程,为monitorenter指令生成汇编代码的生成函数如下:
void TemplateTable::monitorenter() {
transition(atos, vtos);
// 省略一些代码
// store object
__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
__ lock_object(c_rarg1);
// 省略一些代码
}
lock_object函数在InterpreterMacroAssembler类中实现如下:
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
if (UseHeavyMonitors) {
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
} else {
// 省略一些代码
if (UseBiasedLocking) {
biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
}
// 省略一些代码
bind(slow_case);
// Call the runtime routine for slow case
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
bind(done);
}
}
该函数对三种情况做了分别的处理:
- 如果明确使用重量级的监视器(虚拟机参数-XX:+UseHeavyMonitors),那么UseHeavyMonitors的值是true,而UseBiasedLocking的值是false,会直接执行标准的加锁流程(见下文);
- 如果使用偏向锁(默认启用),那么UseHeavyMonitors的值是false,而UseBiasedLocking的值是true。当某个线程想锁定一个可偏向的对象时首先会通过CAS操作尝试将自己的线程指针安装到对象的mark word,如果CAS操作成功则该对象被锁定并偏向了该线程,该线程在后续的加锁与解锁操作时如果对象仍然可偏向,那么只需检查mark word中的线程指针。CAS操作失败意味着该对象已经偏向了另一线程,因此该对象的偏向锁需要被撤销,偏向锁升级成轻量级锁(仍由另一线程持有);
- 如果不使用偏向锁(虚拟机参数-XX:-UseBiasedLocking),则UseHeavyMonitors与UseBiasedLocking的值都是false,执行标准的加锁流程。
monitorexit指令
为monitorexit指令生成汇编代码的函数如下:
void TemplateTable::monitorexit() {
// 省略一些代码
__ unlock_object(c_rarg1);
__ pop_ptr(rax); // discard object
}
所调用的InterpreterMacroAssembler类的unlock_object函数实现如下,与上文的lock_object函数相似。
void InterpreterMacroAssembler::unlock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");
if (UseHeavyMonitors) {
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
lock_reg);
} else {
// 省略一些代码
// Call the runtime routine for slow case.
movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()),
obj_reg); // restore obj
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
lock_reg);
bind(done);
restore_bcp();
}
}
synchronized方法
模板解释器初始化时generate系列函数会为Java方法调用生成汇编代码,以调用普通Java方法为例,generate_normal_entry函数只有一个用来表示方法是否是同步方法的布尔型参数。如果是同步方法那么在调用真正的Java方法之前会调用lock_method函数,注意lock_method函数在内部也会调用InterpreterMacroAssembler类的lock_object函数。
void InterpreterGenerator::lock_method(void) {
// synchronize method
const Address access_flags(rbx, Method::access_flags_offset());
const Address monitor_block_top(
rbp,
frame::interpreter_frame_monitor_block_top_offset * wordSize);
const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;
// get synchronization object
{
const int mirror_offset = in_bytes(Klass::java_mirror_offset());
Label done;
__ movl(rax, access_flags);
__ testl(rax, JVM_ACC_STATIC);
// get receiver (assume this is frequent case)
__ movptr(rax, Address(r14, Interpreter::local_offset_in_bytes(0)));
__ jcc(Assembler::zero, done);
__ movptr(rax, Address(rbx, Method::const_offset()));
__ movptr(rax, Address(rax, ConstMethod::constants_offset()));
__ movptr(rax, Address(rax,
ConstantPool::pool_holder_offset_in_bytes()));
__ movptr(rax, Address(rax, mirror_offset));
__ bind(done);
}
// add space for monitor & lock
__ subptr(rsp, entry_size); // add space for a monitor entry
__ movptr(monitor_block_top, rsp); // set new monitor block top
// store object
__ movptr(Address(rsp, BasicObjectLock::obj_offset_in_bytes()), rax);
__ movptr(c_rarg1, rsp); // object address
__ lock_object(c_rarg1);
}
在深入InterpreterRuntime类的monitorenter和monitorexit静态函数之前,先看一下OpenJDK的Wiki对同步的解释。
同步流程
OpenJDK给出了同步加锁的流程。
- 右边展示了标准的加锁过程。当对象未被加锁时,mark word最低两位的值是01。当方法在对象上同步时,mark word和对象指针会保存在当前栈桢的锁记录(lock record)里。然后虚拟机尝试利用CAS操作将锁记录的指针安装到对象的mark word上,如果成功,当前线程就拥有了锁。锁记录总是在字边界对齐的,mark word的最低两位会变成00以标识该对象被锁定。
- 如果因为该对象之前被锁定而导致CAS操作失败,虚拟机首先会测试mark word是否指向当前线程的方法栈。如果是则这个线程已经拥有了该对象的锁(锁记录已经在当前栈桢中了),可以继续执行。对这种递归锁定的对象,锁记录会被初始化成0而不是对象的mark word。只有当两个不同线程并发地在同一对象上同步时,轻量级锁才必须膨胀成重量级的监视器(monitor)以管理等待的线程。
- 轻量级锁比膨胀的监视器要轻量得多,但性能也受每次CAS操作的影响,尽管大多数对象都是被同一线程锁定和解锁。在Java 6,这个不足被无存储的偏向锁技术解决,只有第一次获取锁时才执行CAS操作用以将线程ID安装到mark word上。对象偏向了这个线程,后续被同一线程加锁与解锁都不需要任何的原子操作或者去更新mark word,栈上的锁记录会一直不被初始化。
- 当一个线程在偏向另一线程的对象上同步时,必须撤回偏向以使该对象被正常地加锁。遍历偏向持有者的栈,与该对象有关的锁记录会被调整成轻量级锁模式,最老的锁记录的指针会被安装到对象的mark word,所有线程必须等待此操作。偏向被撤回的另一种情况是访问对象的hashcode时,因为hash与线程ID在字段上是共享的。
- 显式需要在线程间共享的对象,比如生产者/消费者模式中,不适用偏向锁。因此,在过去如果经常发生撤回那么某个类就会禁用偏向锁,这叫做批量撤回(bulk revocation)。如果加锁代码在被禁用偏向锁的类实例上被调用,那么会执行标准的轻量级锁操作。
- 相似地,批量重偏向(bulk rebiasing)使类实例的偏向无效而不禁用偏向锁,epoch值扮演着偏向有效期的时间戳。这个值会在对象分配的时候拷贝到mark word。
InterpreterRuntime类
InterpreterRuntime类的实现在文件hotspot/src/share/vm/interpreter/interpreterRuntime.cpp中,monitorenter函数代码经过预处理后如下,UseBiasedLocking是运行时参数,默认是开启的。如果使用偏向锁,则调用ObjectSynchronizer类的fast_enter静态函数,否则调用slow_enter静态函数,BasicObjectLock就是上面所说的锁记录实现。
void InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)
{
ThreadInVMfromJavaNoAsyncException __tiv(thread);
HandleMarkCleaner __hm(thread);
Thread* __the_thread__ = thread;
os::verify_stack_alignment();
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, __the_thread__);
if ((((ThreadShadow*)__the_thread__)->has_pending_exception())) return ; (void)(0);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), __the_thread__);
if ((((ThreadShadow*)__the_thread__)->has_pending_exception())) return ; (void)(0);
}
}
monitorexit函数代码经过预处理后如下,只调用ObjectSynchronizer类的slow_exit静态函数。
void InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)
{
ThreadInVMfromJavaNoAsyncException __tiv(thread);
HandleMarkCleaner __hm(thread);
Thread* __the_thread__ = thread;
os::verify_stack_alignment();
Handle h_obj(thread, elem->obj());
if (elem == __null || h_obj()->is_unlocked()) {
{ Exceptions::_throw_msg(__the_thread__, vmSymbols::java_lang_IllegalMonitorStateException(), __null); return; };
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
elem->set_obj(__null);
}
ObjectSynchronizer类
fast_enter函数
在偏向锁启用的情况下,fast_enter函数首先再次尝试撤回偏向并重新偏向至参数线程,若成功则返回,否则执行标准的加锁过程。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter (obj, lock, THREAD) ;
}
slow_enter函数
slow_enter函数即对应着标准的加锁过程,其代码如下所示:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) { // 检查是否为无锁状态,即obj的mark word的后两位是否为01
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark); // 栈桢锁记录的displaced header会保存obj的mark word
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { // 利用CAS操作将锁记录指针安装到obj的mark word上,若操作成功则拥有了锁
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else// 若obj已被锁定,即obj的mark word的后两位已经是00,那么看它是否由参数线程锁定,即检查mark word是否指向参数线程的方法栈
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { // 参数线程重入获得了锁
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL); // 锁记录的displaced header会被置为0而不会是对象的mark word
return;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); // 膨胀成重量级的监视器
}
slow_exit函数
slow_exit与fast_exit函数几乎相同,都是用于monitorexit字节码指令的执行。与slow_enter函数相似,fast_exit函数同样对锁的不同情况做了分类讨论。
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
// if displaced header is null, the previous enter is recursive enter, no-op
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) { // 在slow_enter函数里,递归重入时锁记录的displaced header会被初始化成0
// Recursive stack-lock.
// Diagnostics -- Could be: stack-locked, inflating, inflated.
mark = object->mark() ; // 对象自己的mark word
assert (!mark->is_neutral(), "invariant") ; // 对象肯定是被锁定的状态
if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
}
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor() ;
assert(((oop)(m->object()))->mark() == mark, "invariant") ;
assert(m->is_entered(THREAD), "invariant") ;
}
return ;
}
mark = object->mark() ; // 对象自己的mark word
// If the object is stack-locked by the current thread, try to
// swing the displaced header from the box back to the mark.
if (mark == (markOop) lock) { // 如果是从无锁转变到轻量级锁,那么锁记录的displaced header保存的是对象加锁之前的mark word
assert (dhw->is_neutral(), "invariant") ; // 对象加锁之前的mark word后两位一定是01
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) { // 将对象的mark word恢复到加锁之前的状态
TEVENT (fast_exit: release stacklock) ;
return;
}
}
// 如果不是上述两种情况,那么该释放重量级监视器
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
inflate函数
inflate静态函数用于返回对象的监视器,其代码如下所示:
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
for (;;) {
const markOop mark = object->mark() ; // 对象的mark word
assert (!mark->has_bias_pattern(), "invariant") ; // 一定不是偏向锁
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated
if (mark->has_monitor()) { // 已经是监视器锁状态,mark word已经指向了监视器,即最后两位是10
ObjectMonitor * inf = mark->monitor() ; // 先前填充过的监视器
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ; // 先前填充过的监视器属于参数object
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf ;
}
// CASE: inflation in progress - inflating over a stack-lock.
// Some other thread is converting from stack-locked to inflated.
// Only that thread can complete inflation -- other threads must wait.
// The INFLATING value is transient.
// Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
// We could always eliminate polling by parking the thread on some auxiliary list.
if (mark == markOopDesc::INFLATING()) { // 正在填充,需要重试,markOopDesc::INFLATING()返回0
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
// CASE: stack-locked
// Could be stack-locked either by this thread or by some other thread.
//
// Note that we allocate the objectmonitor speculatively, _before_ attempting
// to install INFLATING into the mark word. We originally installed INFLATING,
// allocated the objectmonitor, and then finally STed the address of the
// objectmonitor into the mark. This was correct, but artificially lengthened
// the interval in which INFLATED appeared in the mark, thus increasing
// the odds of inflation contention.
//
// We now use per-thread private objectmonitor free lists.
// These list are reprovisioned from the global free list outside the
// critical INFLATING...ST interval. A thread can transfer
// multiple objectmonitors en-mass from the global free list to its local free list.
// This reduces coherency traffic and lock contention on the global free list.
// Using such local free lists, it doesn't matter if the omAlloc() call appears
// before or after the CAS(INFLATING) operation.
// See the comments in omAlloc().
if (mark->has_locker()) { // 参数object被轻量级锁锁定,这里不需要管是被谁锁定的,因为这个函数只是将轻量锁膨胀成监视器锁
ObjectMonitor * m = omAlloc (Self) ;
// Optimistically prepare the objectmonitor - anticipate successful CAS
// We do this before the CAS in order to minimize the length of time
// in which INFLATING appears in the mark.
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ; // _owner是锁记录指针,见1312行
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
// We've successfully installed INFLATING (0) into the mark-word.
// This is the only case where 0 will appear in a mark-work.
// Only the singular thread that successfully swings the mark-word
// to 0 can perform (or more precisely, complete) inflation.
//
// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header. Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor. The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants. If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate. The owner
// will then spin, waiting for the 0 value to disappear. Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress. This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator.
// fetch the displaced mark from the owner's stack.
// The owner can't die or unwind past the lock while our INFLATING
// object is in the mark. Furthermore the owner can't complete
// an unlock on the object, either.
markOop dmw = mark->displaced_mark_helper() ; // 从无锁到轻量级锁时栈桢锁记录的displaced header所保存的参数object的原mark word
assert (dmw->is_neutral(), "invariant") ; // 上面一行的原mark word一定是无锁的
// Setup monitor fields to proper values -- prepare the monitor
m->set_header(dmw) ;
// Optimization: if the mark->locker stack address is associated
// with this thread we could simply set m->_owner = Self and
// m->OwnerIsThread = 1. Note that a thread can inflate an object
// that it has stack-locked -- as might happen in wait() -- directly
// with CAS. That is, we can avoid the xchg-NULL .... ST idiom.
m->set_owner(mark->locker()); // _owner是参数object的mark word里的锁记录指针
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
// Must preserve store ordering. The monitor state must
// be stable at the time of publishing the monitor address.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m));
// 省略一些代码
return m ;
}
// CASE: neutral
// TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
// If we know we're inflating for entry it's better to inflate by swinging a
// pre-locked objectMonitor pointer into the object header. A successful
// CAS inflates the object *and* confers ownership to the inflating thread.
// In the current implementation we use a 2-step mechanism where we CAS()
// to inflate and then CAS() again to try to swing _owner from NULL to Self.
// An inflateTry() method that we could call from fast_enter() and slow_enter()
// would be useful.
assert (mark->is_neutral(), "invariant"); // 无锁状态,尚未给参数object分配监视器
ObjectMonitor * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
m->Recycle();
m->set_header(mark); // 新建的监视器_header字段保存的是无锁时参数object的mark word
m->set_owner(NULL); // 初值为NULL
m->set_object(object);
m->OwnerIsThread = 1 ; // _owner是线程指针
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
// 使用CAS操作将监视器指针安装到参数object的mark word,若失败则重试
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
// interference - the markword changed - just retry.
// The state-transitions are one-way, so there's no chance of
// live-lock -- "Inflated" is an absorbing state.
}
// 省略一些代码
return m ;
}
}
根据对象mark word的不同状态,inflate函数有不同的处理方式:
- 已经是监视器锁状态:直接返回先前的监视器;
- 正在填充(值为0):重试;
- 已经被轻量锁锁定:将轻量锁膨胀成监视器;
- 无锁状态:新建监视器并关联该对象。
获得与释放监视器请见后续文章。
参考文献
[1] David Dice. Implementing Fast Java Monitors with Relaxed-Locks. 2001
[2] JVM同步方法之偏向锁 https://zhuanlan.zhihu.com/p/34662715