一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
前言
深入理解synchronized(一)——初识synchronized 在上一篇文章中,我们介绍了synchronized的作用以及用法,本文我们来探究下synchronized到底如何实现在对象加锁的。
Monitor
Monitor可以理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。 与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。
synchronized就是通过Monitor(管程)给对象加锁的,首先我们来查看synchronized方法与非synchronized方法,查看反汇编后的字节码文件的区别。 类Test如下,其包含普通的test1()方法与包含同步synchronized代码块的方法test2()
public class Test {
public void test1() {
}
public synchronized void test2() {
synchronized (this) {
}
}
}
复制代码
通过javac与javap获取反汇编的代码:
javac Test.class
javap -c Test
复制代码
结果如下:
可以看到test1()与test2()的代码存在不同,我们主要关注test2()方法增加了monitorenter与monitorexit指令,应该可以猜到这两个指令就是实现线程同步的关键,monitorenter表示同步开始的地方,执行到此指令时,线程会尝试获取对象的锁也就是JVM底层用c++实现的ObjectMonitor对象的所有权,一旦线程获取对象的monitor,在释放该monitor前,其他线程将无法获取到该monitor,从而实现线程之间的同步。而执行到monitorexit时,线程会释放对monitorexit的所有权。 问题:为什么会有两个monitorexit? 前一个monitorexit用于正常情况时退出同步状态,后一个monitorexit用于异常情况退出同步状态。
ObjectMonitor中各个区域线程的流转,其中提到了
ObjectMonitor是JVM底层用c++实现Monitor的对象,java对象头信息中的Mask Word处存储了ObjectMonitor的指针,每个java对象都有一个ObjectMonitor与之关联。我们看看其结构:
//结构体如下
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //线程的重入次数
- _count:用来记录该线程获取锁的次数
_object = NULL;
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
复制代码
首先我们了解下其主要字段的含义:
- _owner:标识拥有该monitor的线程
- _WaitSet:等待线程组成的双向循环链表,_WaitSet是第一个节点,存放处于wait状态的线程队列
- _EntryList:_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点,存放处于等待锁block状态的线程队列
- _recursions:线程的重入次数
- _count:用来记录该线程获取锁的次数
- _cxq: 多线程竞争锁进入时的单向链表
当多个线程争抢一个锁时,这些线程首先会进入_cxq队列,而_cxq中的线程会有一些策略筛选,筛选部分有资格获取锁的线程进入_EntryList队列,当_EntryList中某个线程获取到对象的monitor时,当前线程会进入owner区域,并且会将_owner变量置为当前线程,同时_count会加1,如果当前线程是重新进入,则_recursions也会加1,而如果持有monitor的线程因一些条件需要等待(调用wait())时,会释放monitor并进入_WaitSet.处于_WaitSet区域的线程在收到通知(notify/notifyAll方法),对应的线程会从该区域进入到_EntryList区域中。当持有monitor的线程释放monitor时,处于_EntryList中的线程会开始抢占该monitor,但是只能有任意的一个Thread能取得该锁,而其他线程依然在_EntryList中等待下次来抢占到锁之后再执行。
上面我们介绍了ObjectMonitor中各个区域线程的流转,其中提到了_owner与_WaitSet、_EntryList之间会通过wait与notify/notifyAll进行通知或改变状态,下面我们简单介绍下wait()与notify()的实现。
wait()的实现
wait()的实现大致分为以下几步:
- 当前线程获取到对象的ObjectMonitor对象(锁),然后调用ObjectMonitor::wait()方法。
- ObjectMonitor::wait()方法会将当前线程插入到当前_WaitSet的尾部。
- 通过ObjectMonitor::exit方法释放对ObjectMonitor对象(锁)的占用。
//1.当前线程获取到对象的ObjectMonitor对象(锁),然后调用ObjectMonitor::wait()方法啊。
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
/*省略 */
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
monitor->wait(millis, true, THREAD);
/*省略*/
}
//2.ObjectMonitor::wait()方法会将当前线程插入到当前_WaitSet的尾部。
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
/*省略*/
if (_WaitSet == NULL) {
//_WaitSet为null,就初始化_waitSet
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
//否则就尾插
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
//3.通过ObjectMonitor::exit方法释放对ObjectMonitor对象(锁)的占用。
复制代码
notify()的实现
notify()的实现大致分为以下几步:
- 当前持有ObjecyMonitor的线程获取ObjectMonitor锁,调用ObjectMonitor的notify方法。
- 将_WaiterSet中第一个线程节点移出。
- 将移除的线程节点插入_EntryList尾部。
//1.当前持有ObjecyMonitor的线程获取ObjectMonitor锁,调用ObjectMonitor的notify方法。
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
/*省略*/
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
/*省略*/
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
return inf
}
/*省略*/
}
void ObjectMonitor::notify(TRAPS) {
/*省略*/
//2.将_WaiterSet中第一个线程节点移出。
ObjectWaiter * iterator = DequeueWaiter() ;
//3.将移除的线程节点插入_EntryList尾部。
/**省略*/
}
复制代码
结语
本文介绍了synchronized的同步实现原理-Monitor,对JVM底层的ObjectMonitor的结构以及如何实现线程之间的同步和线程争抢锁的流程进行了介绍,经过本文,大家应该对synchronized的理解有所加深。