Synchronized 底层原理
- Synchronized的语义底层是通过一个 Monitor 的对象来完成,其实wait/notify等方法也依赖于 Monitor 对象,这就是为什么只有在同步的块中,拿到锁之后,才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
- 下面会具体讲解 Monitor 是什么
Java 对象头
-  以32位虚拟机为例: 
-  普通对象: ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GFb19ABJ-1673008401334)(深入理解Synchronized.assets/20210130134419113.png)]](https://img-blog.csdnimg.cn/1bf72595e2e14fee8c8f9911082456e3.png) 
-  数组对象: ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0GMbs2b8-1673008401335)(深入理解Synchronized.assets/20210130134453728.png)]](https://img-blog.csdnimg.cn/129eec89218a4e6a973a5220fd3baa16.png) 
-  其中 Mark Word 的结构,如下图所示: ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGq86KDh-1673008401335)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70.png)]](https://img-blog.csdnimg.cn/48ebb2c556c747ffa1a8ba7b3dbb3a00.png) 
-  所以一个 Java 对象的结构如下: ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2Jsi7gL-1673008401336)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-16729984581845.png)]](https://img-blog.csdnimg.cn/469e9d3cf9ba4e79bd57dd1bd1ad1a26.png) 
Monitor
- Monitor 被翻译为监视器或者说管程
- 每个 java 对象都可以关联一个 Monitor ,如果使用 synchronized 给对象上锁(重量级),该 Java 对象头的 Mark Word 中就被设置为指向 Monitor 对象的指针。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7K0iZ1V-1673008401336)(深入理解Synchronized.assets/20200608144917.png)]](https://img-blog.csdnimg.cn/6d6c0bcce4e0443dae9aa1546bda1c34.png)
- 当线程执行到临界区代码时,如果使用了synchronized,会先查询synchronized中所指定的对象(obj)是否绑定了Monitor。 
  - 如果没有绑定,则会先去与Monitor绑定,并且将Owner设为当前线程。
- 如果已经绑定,则会去查询该Monitor是否已经有了Owner 
    - 如果没有,则Owner与将当前线程绑定
- 如果有,则放入EntryList,进入阻塞状态(blocked)
 
 
- 当Monitor的Owner将临界区中代码执行完毕后,Owner便会被清空,此时EntryList中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的。什么是非公平呢?非公平就是无法保证先来的线程优先获取锁。公平锁:公平锁就是保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁。
Synchronized 的优化(jdk 6)
轻量级锁
-  调用 synchronized 去加锁的时候,会优先使用轻量级锁去加锁,如果失败了,才会使用重量级锁去加锁 
-  轻量级锁的使用场景是:如果一个对象虽然有多个线程要对它进行加锁,但是加锁的时间是错开的(也就是没有线程竞争),那么可以使用轻量级锁来进行优化。轻量级锁对使用者是透明的,即语法仍然是 synchronized 
-  具体加锁过程: -  每次运行到 synchronized 代码块时,都会在线程的栈中创建锁记录(Lock Record)对象,每个线程都会包括一个锁记录的结构,锁记录内部可以储存对象的 Mark Word 和对象引用 reference ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eY8vh0XL-1673008401337)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-16730063937947.png)]](https://img-blog.csdnimg.cn/4698cec575a847758a10233f352b1bd5.png) 
-  让锁记录中的 Object reference 指向对象,并且尝试用 cas(compare and swap) 替换 Object 对象的 Mark Word ,将 Mark Word 的值存入锁记录中。 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0fsbJ6sn-1673008401337)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-16730064153079.png)]](https://img-blog.csdnimg.cn/32abb6950fb7418d973ddb7bee297773.png) 
-  如果 cas 替换成功,那么对象的对象头储存的就是锁记录的地址和 State状态 00 表示轻量级锁,如下所示 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VFKzL6jm-1673008401337)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300644500411.png)]](https://img-blog.csdnimg.cn/34099fea7d56485dbc5059431b672326.png) 
-  如果cas失败,有两种情况 
-  1.如果是其它线程已经持有了该 Object 的轻量级锁,那么表示有竞争,首先会进行自旋锁,自旋一定次数后,如果还是失败就进入锁膨胀阶段。 
-  2.如果是自己的线程已经执行了 synchronized 进行加锁,那么再添加一条 Lock Record 作为重入的计数。 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eL7aeQ9m-1673008401337)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300652966613.png)]](https://img-blog.csdnimg.cn/d6f1a2b8ed684818af955412ffd01337.png) 
-  当线程退出 synchronized 代码块的时候,如果获取的是取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-llEjw2rZ-1673008401338)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300655065415.png)]](https://img-blog.csdnimg.cn/bfb6ab65031d4459b2952294e61c1a3c.png) 
-  当线程退出 synchronized 代码块的时候,如果获取的锁记录取值不为 null,那么使用 cas 将 Mark Word 的值恢复给对象 - 成功则解锁成功
- 失败,则说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
 
 
-  
锁膨胀
-  如果在尝试加轻量级锁的过程中,cas 操作无法成功,这是有一种情况就是其它线程已经为这个对象加上了轻量级锁,这时就要进行锁膨胀,将轻量级锁变成重量级锁。 
-  当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lF5ZFdwX-1673008401338)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300669045317.png)]](https://img-blog.csdnimg.cn/99da3f70f9f64f22af8f5017bec64e2b.png) 
-  这时 Thread-1 加轻量级锁失败,进入锁膨胀流程 -  即为对象申请Monitor锁,让Object指向重量级锁地址 
-  然后自己进入Monitor 的EntryList 变成BLOCKED状态 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KukeDXCZ-1673008401338)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300673196419.png)]](https://img-blog.csdnimg.cn/1f9a7e3d05204949b8e98924d195efd9.png) 
 
-  
-  当 Thread-0 退出 synchronized 同步块时,使用 cas 将 Mark Word 的值恢复给对象头,对象的对象头指向 Monitor,那么会进入重量级锁的解锁过程,即按照 Monitor 的地址找到 Monitor 对象,将 Owner 设置为 null ,唤醒 EntryList 中的 Thread-1 线程 
自旋优化
- 重量级锁竞争的时候,没争抢到锁的线程不立即进入 EntryList 阻塞,还可以使用自旋来进行优化,如果当前线程自旋成功(即在自旋的时候持锁的线程释放了锁),那么当前线程就可以不用进行上下文切换就获得了锁
- 自旋重试成功的情况
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZDc0zZr-1673008401339)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300697122921.png)]](https://img-blog.csdnimg.cn/9321d48efe4a41bdaab83f5189d2faf2.png)
- 自旋重试失败的情况,自旋了一定次数还是没有等到持锁的线程释放锁
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d98gkRJ0-1673008401339)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300698508823.png)]](https://img-blog.csdnimg.cn/bfe4845164ba4494b0325ab8025042f2.png)
- 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。Java 7 之后不能控制是否开启自旋功能
偏向锁
- 在轻量级锁中,我们可以发现,如果同一个线程对同一个对象进行重入锁时,也需要执行 CAS 操作,这是有点耗时滴,那么 java6 开始引入了偏向锁,只有第一次使用 CAS 时将对象的 Mark Word 头设置为偏向线程 ID,之后这个入锁线程再进行重入锁时,发现线程 ID 是自己的,那么就不用再进行CAS了。
- 分析代码,比较轻量级锁与偏向锁
static final Object obj = new Object();
public static void m1() {
	synchronized(obj) {
		// 同步块 A
		m2();
	}
}
public static void m2() {
	synchronized(obj) {
		// 同步块 B
		m3();
	}
}
public static void m3() {
	synchronized(obj) {
		// 同步块 C
	}
}
- 分析如图:
![**[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVOmmzpP-1673008401339)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300719539725.png)]](https://img-blog.csdnimg.cn/4e04d155552e48838be0450016426ba1.png)
- 偏向锁的对象头格式如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6aT9Lp2I-1673008401339)(深入理解Synchronized.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDI4MDU3Ng==,size_16,color_FFFFFF,t_70-167300725221627.png)]](https://img-blog.csdnimg.cn/8066496bee97408f8ae817545ef96fb2.png)
- 一个对象的创建过程 
  - 如果开启了偏向锁(默认是开启的),那么对象刚创建之后,Mark Word 最后三位的值101,并且这是它的 Thread,epoch,age 都是 0 ,在加锁的时候进行设置这些的值.
- 偏向锁默认是延迟的,不会在程序启动的时候立刻生效,如果想避免延迟,可以添加虚拟机参数来禁用延迟:-XX:BiasedLockingStartupDelay=0 来禁用延迟
- 注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
 
撤销偏向锁
-  以下几种情况会使对象的偏向锁失效 -  调用对象的 hashCode 方法 
-  其他线程使用该对象,会将偏向锁升级为轻量级锁 
-  调用了 wait/notify 方法(调用wait方法会导致锁膨胀而使用重量级锁) 
 
-  
批量重偏向
- 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向 t1 的对象仍有机会重新偏向 t2 
  - 重偏向会重置Thread ID
 
- 当撤销超过20次后(超过阈值),JVM 会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程。
批量撤销
- 当撤销偏向锁的阈值超过 40 以后,就会将整个类的对象都改为不可偏向的
锁撤销
- JIT 即时编译器,会进行优化
d ID
- 当撤销超过20次后(超过阈值),JVM 会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程。
批量撤销
- 当撤销偏向锁的阈值超过 40 以后,就会将整个类的对象都改为不可偏向的
锁撤销
- JIT 即时编译器,会进行优化



















