引言
虽然我们平常在解决多线程并发问题的时候都使用过 Java 中提供的 synchronized 关键字。但是当被提问到底层是如何实现时,却不知道从何谈起,这篇文章带你了解一下 synchronized 的底层实现。
Java对象头
要想了解 synchronized 是怎么实现的要先了解一下 Java对象头的内容。
-
Klass Word: 每个对象都有自己的类型,比如 Student、Teacher 等等,这些类型都是由后 32 位(4个字节)的
Klass Word
所表示的。它是一个指针,指向了对象所从属的 class。 -
Mark Word结构:
- hashcode:就是哈希码
- age:表示在垃圾回收中的分代年龄
- biased_lock:表示的是偏向锁的状态
- 最后两位表示加锁状态
当加的锁是不同的锁时,Mark Word 的结构也会各有不同,主要有上述的一些状态,State 有标注。具体下次介绍。
Monitor
说完对象头,我们在了解一下 Monitor 的结构。
每一个对象都可以关联一个 Monitor
对象,如果使用 synchronized 给对象加锁时(重量级锁),该对象头的 Mark Word
就是被设置为指向Monitor
的对象指针。
Monitor 的结构如下:
主要分为三个区域:
- Owner:指向成功获取锁的线程
- EntryList:阻塞队列,表示没有竞争到锁的线程都会进入到阻塞队列中
- WaitSet:获得锁后,调用了 wait 方法的线程都会进入到 WaitSet当中。
大致流程
- 当一个线程进入到了 synchronized 会进行加锁(假设重量级锁),这时这个对象头的
Mark Word
会指向一个Monitor
对象。这时候对象头的结构为: - 由于这时候没有别的线程过来竞争,所以
Monitor
对象的Owner
会指向获取锁的该线程。 - 然后当这个线程还没有释放锁的时候,有别的线程进入到 synchronized 进行加锁获取对象时,发现这个对象头已经指向了有
Monitor
对象,并且Owner
也指向别的一个线程,这时候这个线程就会进入到EntryList
中进行阻塞。 - 当获取锁的线程执行完成之后,会将
Owner
置为 null,这时EntryList
中的线程会进入锁竞争(非公平的),抢到锁的线程会进行下一步,如果没有抢到锁还是会呆在EntryList
中进行阻塞,等待下一次竞争锁。
注意:
- synchronized 必须进入的是同一个
Monitor
对象才有上述的流程 - 不加 synchronized 的对象不会关联
Monitor
对象,也不会有上面的这些规则。
下期详细介绍 synchronized的优化。