博主主页:爪哇贡尘拾Miraitow
创作时间:2022年2月8日 10:37
内容介绍: synchronized关键字的底层原理
参考资料:gitee上面的文档
⏳简言以励:列位看官,且将新火试新茶,诗酒趁年华
内容较多有问题希望能够不吝赐教
欢迎点赞 收藏 ⭐留言
synchronized原理
当我们使用synchronized关键字来修饰代码块时,字节码层面上是通过monitorenter
与monitorxeit
指令来实现的锁的获取
与释放动作
,当线程进入到monitorenter
指令后,线程将会持有monitor
对象(每个对象都会被操作系统分配monitor),退出monitorenter
指令后,线程将会释放monitor对象
MyTest4.java
对如下代码进行反编译:
查看method()的字节码指令,可以证明我们说的结论
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String --test--
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
synchronized修饰方法
synchronized
关键字修饰方法来说,在方法的字节码指令中没有出现monitorenter
与monitorexit
指令,而是出现了一个ACC_SYNCHRONIZED
标志。JVM使用了ACC_SYNCHRONIZED
访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥ACC_SYNCHRONIZED
标志,如果有,那么执行线程将会先持有方法所在对象的monitor对象
,然后再去执行方法体
;在该方法执行时间,其它任何线程均无法再获取到这个monitor对象,当线程执行完该方法后,他会释放掉这个monitor对象
。
对如下代码进行反编译:
MyTest5.java
public static synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ----test---
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
JVM中的同步是基于进入与退出监视器对象(管程对象)(monitor)来是实现的
,每个对象实例都会有一个monitor对象,monitor对象会和java对象一同创建并销毁,monitor对象是由c++来实现的
当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于阻塞状态的线程都会被放到该列表当中,接下来,当线程获取到对象的monitor时,monitor是依赖于底层操作系统的mutex lock 来实现互斥的
,线程获取mutex成功,则会持有mutex,这时其它线程就无法再获取到mutex。
如果线程调用了wait方法,那么该线程会释放掉所持有的mutex,并且该线程会进入到WaitSet集合中,等待下一次被其它线程调用notify/notifyAll唤醒。如果当前线程顺利执行完方法,那么它也会释放掉所持有的mutex。
总结一下:同步锁在这种实现方式中,因为monitor是依赖底层操作系统来实现的,这样就存在用户态和内核态之间的切换,所以会增加性能开销。
通过对象互斥锁的概念来保证共享数据操作的完整性,每个对象都对象于一个可称为【互斥锁】的标记,这个标记用于保证在任何时刻,只能有一个线程访问对象。
那些处于EntryList与WaitSet中的线程均处于阻塞状态,阻塞状态是由操作系统来完成的,在linux下是通过pthred_mutx_lock函数实现的,线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态和内核态之间按的切换,严重影响锁的性能。
解决上述问题的办法便是自旋(Spin)
,其原理是:当发生对monitor的争用时,若owner能够在很短的时间内释放掉锁,则那些正在争用的线程就可以稍微等待一下(即所谓的自旋
),在owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞(上下文切换
)。不过,当owner运行的时间超过了临界值
后,争用线程自旋一段时间后仍无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态
,所以总体的思想是:先自旋,不成功在进行阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说所有极大的性能提升,显然,自旋在多核处理器上才有意义。(当然自旋在cpu较多的时候,才能发挥较大的作用,毕竟自旋也要占用cpu的
)
互斥锁的属性:
点我查看 打开在线查看openjdk的源码
有一个ObjectWaiter
类,表示的是一个线程或者说一个代理线程,里面含有指向上一个ObjectWaiter和下一个ObjectWaiter的指针
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
jlong _notifier_tid;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
ObjectMonitor表示的就是moniter对象,里面有 _ WaitSet 集合和 _ EntryList集合,这两个集合都是ObjectWaiter类型,都是表示正在阻塞的线程的集合。
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
打开网址,查看wait方法中的代码:
ObjectWaiter node(Self); //将当前线程封装成ObjectWatier
...
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ; // 自旋操作
AddWaiter (&node) ; // 添加到WaitSet节点中
...
查看AddWaiter()方法,就是将ObjectWaiter对象添加到WaitSet节点中,使用双向链表的数据结构
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
assert(node != NULL, "should not dequeue NULL node");
assert(node->_prev == NULL, "node already in list");
assert(node->_next == NULL, "node already in list");
// put node at end of queue (circular doubly linked list)
if (_WaitSet == NULL) {
_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;
}
}
根据不同的策略,将从WaitSet集合中移出来的ObjectWaiter对象加入到EntryList集合中
void ObjectMonitor::notify(TRAPS) {
...
ObjectWaiter * iterator = DequeueWaiter() ;
...
if (Policy == 0) { // prepend to EntryList
...
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
List->_prev = iterator ;
iterator->_next = List ;
iterator->_prev = NULL ;
_EntryList = iterator ;}
... }
if (Policy == 1) {...} // append to EntryList
if (Policy == 2) {...} // prepend to cxq
if (Policy == 3) {...} // append to cxq
...
}