Synchronized的实现原理
对于被Synchronized修饰的方法/代码块,会多出三个汇编指令:monitorEnter(代码执行前)、monitorExit(代码执行后)、monitorExit(抛出异常时)。
monitorEnter是获取锁,monitorExit释放锁。
为什么每一个对象都能成为锁,锁存在什么地方
Synchronized修饰方法,那么这个锁就是类层面的锁,锁的是当前类的class对象。对于Synchronized代码块,则Synchronized(obj)中的obj就是被锁的对象。每一个java对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。而锁存在于对象头。 对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
获取锁的流程
首先在markword中有个锁标志来表明当前是哪种类型的锁,而走不同的流程。
锁升级和锁膨胀的流程:偏向锁->轻量级锁+自旋锁->重量级锁
偏向锁:为了减少轻量级锁的性能消耗(轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS):一定时间内只有一个线程执行同步代码,不存在竞争。在markword中记录了线程ID和epoch值。线程从无锁状态进入同步代码块时判断有没有记录线程ID,有记录则判断是不是当前线程ID,是就直接获得锁,无记录则进行CAS操作将当前线程ID存入markword中,操作失败则存在竞争升级为轻量级锁。重偏向(待补充)
轻量级锁:为了减少以下场景的性能消耗:线程无实际竞争或竞争的时间很短(时间内自旋可以获得锁)。包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,此时CAS操作自旋一段时间,仍然失败则膨胀为重量级锁。
重量级锁:此时会初始化一个objectMonitor对象监视器,markword中的部分字节会指向这个monitor。monitor包含以下信息:markword,当前对象,当前线程,重入次数,cxq队列,entryList,waitSet等待队列。首先线程获取锁判断owner是否为空,为空则记录为当前线程,不为空判断是不是当前线程,是则重入次数加1。不是则将线程包装成ObjectWaiter对象放入cxq队列,并调用park挂起这个线程。锁释放会根据策略不同从cxq队列或entryList中取一个线程去唤醒去再次竞争锁
wait与notify
wait时线程会被挂起放入waitset队列并释放锁;
notify时从waitSet中取出第一个,根据Policy的不同,将这个线程放入_EntryList或者_cxq队列中的起始或末尾位置,然后根据QMode的不同,将ObjectWaiter从_cxq或者_EntryList中取出后唤醒再次去竞争锁。