锁的表示
Java里的锁,主要都是对对象进行加锁,如普通的synchronized非静态方法,就是对当前执行方法的对象进行加锁。那么怎么对对象进行加锁呢?对象的锁其实主要就是通过对象头的markOop进行表示的。markOop其实不是一个对象,只是一个字长的数据,在32为机器上,markOop为32个位,在64位上为64个位。markOop中不同的位区域存储着不同的信息,但是需要注意的一点是,markOop每个位区域表示的信息不是一定的,在不同状态下,markWord中存着不同的信息。接下来盗图一张:
![](https://img.haomeiwen.com/i3135619/bf5b089a13bd469d.png)
偏向锁
当锁对象第一次被某个线程访问时,它会在其对象头的markOop中记录该线程ID,那么下次该线程再次访问它时,就不需要进行加锁了。但是这中间只要发生了其他线程访问该锁对象的情况,证明这个对象会发生并发,就不能对这个对象再使用偏向锁了,会进行锁的升级。
一句话,有线程请求锁,就在对象头记录此线程ID,就算一直占用了,除非其他线程请求锁,否则永不释放。
轻量锁
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
一句话,线程栈中有个对象,有线程请求锁,就在对象头中cas指向线程栈中的这个对象,cas成功了就获得了锁。
重量级锁
轻量级锁膨胀之后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
为什么说重量级锁开销大呢?主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
在 Hotspot 中这些操作是通过 ObjectMonitor 来实现的,通过它提供的功能就可能做到获取锁,释放锁,阻塞中等待锁释放再去竞争锁,锁等待被唤醒等功能,我们来探讨下它是如何做到的。
每个对象都持有一个 Monitor, Monitor 是一种同步机制,通过它我们就可以实现线程之间的互斥访问,首先来列举下 ObjectMonitor 的几个我们需要讨论的关键字段
_owner,ObjectMonitor 目前被哪个线程持有
_entryList,阻塞队列(阻塞竞争获取锁的一些线程)
_WaitSet,等待队列中的线程需要等待被唤醒(可以通过中断,singal,超时返回等)
_cxq,线程获取锁失败放入 _cxq 队列中
_recursions,线程重入次数,synchronized 是个可重入锁
为每一个对象创建一个monitor结构,在堆中。
1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址
2.Monitor的Owner字段会存放拥有相关联对象锁的线程id
网友评论