- sync到底是对什么加锁
- Monitor模型
2.1 为什么sync是非公平锁
2.2 Monitor和对象头的关系
2.3 Monitor的JDK源码 - sync各种锁与Monitor的关系
- wait和notify方法的疑问
4.1 wait和notify为什么必须synchronized中使用
4.2 wait和notify为什么是Object的方法
1. sync到底是对什么加锁
JAVA虚拟机给每个对象和class字节码文件都设置了一个监控器Monitor,用于检测并发代码的重入。同时,Object类中提供notify和wait方法来对Monitor中的线程进行控制。
sync锁是一个可重入的非公平独占锁。sync加锁(此处特指重量级锁)会去获取obj的Monitor,如果Monitor已经被其他线程获取,那么当前线程会进入Entry Set
。等待其他线程释放obj的Monitor。
而这里的Monitor可以是类.class的Monitor,也可以是当前对象(this)Monitor。
也就是可以回答上面的问题:锁实例方法是同一个对象互斥,锁静态方法是全局互斥。
- 作用于实例方法时,锁住的是this对象为锁的所有代码块;
- 作用于静态方法时,锁住的是Class实例,又因为Class相关数据存储在永久代,永久代是全局贡献,因此静态方法相当于类的一个全局锁,会锁住调用该方法的所有线程;
- 作用于一个对象实例时,锁住的是所有以该对象为锁的所有代码块;
即sync借助obj中的Monitor完成线程的阻塞。
2. Monitor模型
Monitor实现对临界资源的保护,保证每次只有一个线程能进入代码块进行访问。进入代码块即为持有Monitor,退出代码块即为释放Monitor。
2.1 为什么sync是非公平锁
而未抢占到锁的资源,便会进入Monitor的Entry Set阻塞。当抢占到锁的方法遇到wait()方法后,会释放Monitor资源,并进入到Wait Set阻塞。当Monitor资源被释放后。Entry Set、Wait Set和刚进入Monitor的线程共同争夺Monitor资源。这就是sync是非公平锁的原因。
2.2 Monitor和对象头的关系
sync
是借助obj的monitor
对象实现当前线程的阻塞。代码块加锁是在前后分别加上monitorentry
和monitorexit
指令来实现的。obj被加锁后,可以在其对象头的mark word
的标记位体现。
2.3 Monitor的JDK源码
JDK源码如下图所示,可以看到Wait Set、Entry Set、count(重入次数)、owner(持有锁的线程)等属性。
ObjectMonitor() {
_header = NULL;
//获取管程锁的次数
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
//持有该ObjectMonitor线程的指针
_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;
}
3. sync各种锁与Monitor的关系
偏向锁和轻量级锁的目的:偏向锁、轻量级锁并不是来取代重量级锁的。而是在不同的场景下的相互补充。偏向锁和轻量级锁解决的是当临界资源没有被争夺访问的场景下,如何优化性能。
偏向锁和轻量级锁的实现:通过对象头mark word
的标记位,通过CAS来实现访问。无需借助Monitor。
锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀。
只有一个线程访问sync保护的临界资源。而偏向锁的目的是某个线程获取锁后,消除这个线程重入的开销。偏向锁只需要在置换ThreadId的时候依赖一次CAS。
两个线程交替访问,没有多线程竞争的前提下,减少传统重量级锁产生的性能损耗。
若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
借助obj的Monitor对象完成线程的互斥访问。而线程的阻塞借助操作系统完成。这个过程是比较耗费事件的。
在JDK1.6之后,sync关键字进行了优化,使用了自旋锁,让并发的线程先不借助monitor进行同步操作。而是自旋一段时间,等待临界区线程执行完毕。
4. wait和notify方法的疑问
4.1 wait和notify为什么必须synchronized中使用
只因为synchronized关键字(重量级锁,开启了管程),使得对象指向了ObjectMonitor对象,所以调用对象的wait()和notify等方法才会将线程阻塞(加入到_WaitSet中)。
4.2 wait和notify为什么是Object的方法
又因为wait()和notify()是ObjectMonitor的方法。而Object对象头中保存了ObjectMonitor的指针,所以是Object便可操作wait()方法。
推荐阅读
Java并发基石——所谓“阻塞”:Object Monitor和AQS(1)
简书—信号量与管程
Java精通并发-通过openjdk源码分析ObjectMonitor底层实现
相关阅读
JAVA并发(1)—java对象布局
JAVA并发(2)—sync关键字和Monitor管程的关系
JAVA并发(3)—线程运行时发生GC,会回收ThreadLocal弱引用的key吗?
JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!
JAVA并发(5)— 多线程顺序的打印出A,B,C(线程间的协作)
JAVA并发(6)— AQS源码解析(独占锁-加锁过程)
JAVA并发(7)—AQS源码解析(独占锁-解锁过程)
JAVA并发(8)—AQS公平锁为什么会比非公平锁效率低(源码分析)
JAVA并发(9)— 共享锁的获取与释放
JAVA并发(10)—interrupt唤醒挂起线程
JAVA并发(11)—AQS源码Condition阻塞和唤醒
JAVA并发(12)— Lock实现生产者消费者