文章目录
前言
并发编程中会涉及到锁的概念,目前接触到的是两套机制,根据现有理解,简单理解一下,后边如果发现有不妥之处,再来纠正
一、第一套机制:基于Monitor的Synchronized + 锁对象 的隐式锁机制
这一套机制立身之本是Monitor
- 每一个对象都有对象头
- 每一个对象都有一个monitor与之关联
- Synchronized 实现锁的机制,本质上是利用了对象头和monitor
- monitor结构详解:
接下来我们首先看下在monitor在Java虚拟机(HotSpot)中的实现,其实现是基于C++的,由ObjectMonitor实现的,其主要数据结构如下:
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 ;
}
在上面的源码我们可以看到ObjectMonitor中有几个关键属性:
- _owner:指向持有ObjectMonitor对象的线程
- _WaitSet:存放处于wait状态的线程队列
- _EntryList:存放处于等待锁block状态的线程队列
- _recursions:锁的重入次数
- _count:用来记录该线程获取锁的次数
当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得对象锁。
若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:
上边关于ObjectMonitor的结构摘抄自<Moniter的实现原理>,感兴趣可以去看
- 我们可以看出其实Monitor提供了两个集合,
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
其中:
1. _EntryList: 这个集合被 Synchronized 间接调用,例如:
synchronized (锁对象){
代码...去酱油
}
这段代码就是同时出现了 Synchronized + 锁对象, 其实本质上是Synchronized 召唤出了锁对象---->对象头----MarkWord---->Monitor---->_EntryList, 然后就是当第一个线程Threa_1来访问 Synchronized 同步块时,线程Threa_1就被Monitor的_owner标记下来,在Threa_1释放之前,后边再来访问Synchronized 同步块的线程,将会被放入 Monitor的_EntryList中阻塞, 所以说 上边的代码只有一个功能,就是防止多个线程同时进入Synchronized 同步块的功能
2. _WaitSet: 这个集合又是干啥用的呢?,我们看:
synchronized (锁对象){
代码...开始买酱油; //发现商店没有你要的酱油
锁对象.wait(); //等一下,老板调货
代码...终于买到酱油了; //酱油调来了,老板会通知你,然后你也买到了
}
这段代码出现了 锁对象.wait(); 这一句是干啥的呢? 这一句是通过锁对象调用了---->对象头----MarkWord---->Monitor---->_WaitSet, 让当前获得锁的线程先停一下,然后把锁释放出来, 然后自己进入_WaitSet中等待,所以可以看出,其实我们的锁机制还提供了线程的中途等待,就是说,你获得了锁,并不是说你去商店买酱油,一定要买到才能出来,而是如果你发现商店没有,老板说他去调货,你反正也没事,就到商店外的休息区等待老板喊你,等到老板喊你了,你在尝试进来商店,按老板给你说的价格直接买酱油;
- 简单总结:
- Monitor的_EntryList 被synchronized 调用,用来存储阻塞线程
- Monitor的_WaitSet被 锁对象 调用,用来存储主动释放锁,去做等待的线程
- synchronized
二、第二套就是基于AQS的 Lock和Condition 的显式锁机制
这套机制是仿照第一套逻辑做的,但是在具体实现上有所不同,从大的方面来说就是AQS提供了两个队列 [同步队列] , [条件队列]
Lock拦截到的线程都进入[同步队列] 阻塞
Condition 导致等待的线程都进入 [条件队列]
condition 唤醒的线程则是 从 [条件队列] 进入[同步队列] 阻塞
总结
上边两套机制是平行的,实现的功能也是类似,但是第二套机制比第一套机制灵活
对于第二套机制的细节,现在还没有理清楚,但为啥还要总结呢?原因就是发现了第二套机制在功能上和第一套机制是一样的,有了这个认识,在学习第二套机制的时候思路就不会凌乱