一、synchronized锁变化历程
- 在JDK 1.6及之前的版本中,synchronized锁是通过对象内部的一个叫做监视器锁monitor(也称对象锁)来实现的。当一个线程请求对象锁时,如果该对象没有被锁住,线程就会获取锁并继续执行。如果该对象已经被锁住,线程就会进入阻塞状态,直到锁被释放。这种锁的实现方式称为“重量级锁”,因为获取锁和释放锁都需要在操作系统层面上进行线程的阻塞和唤醒,而这些操作会带来很大的开销。
- 在JDK 1.6之后,synchronized锁的实现发生了一些变化,引入了“偏向锁”、“轻量级锁”和“重量级锁”三种不同的状态,用来适应不同场景下的锁竞争情况。
- 在JDK 15后,废弃了偏向锁
二、synchronized锁的状态
在Java中,synchronized锁的状态分为四种:
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
在Java中,对象头的MarkWord的低两位用于表示锁的状态,分别为:
- 01(无锁状态):与偏向锁状态一致,所以在低三位引入偏向锁标记位,用0表示无锁
- 01(偏向锁状态):与无锁状态一致,所以在低三位引入偏向锁标记位,用1表示偏向
- 00(轻量级锁状态)
- 10(重量级锁状态)
三、synchronized锁升级过程
synchronized锁升级过程大概为无锁->偏向锁->轻量级锁->重量级锁
1、无锁 -> 偏向锁
当一个线程第一次访问synchronized锁时,JVM会在对象头的MarkWord中设置该线程的线程ID,并将对象头的状态为设置为偏向锁状态。
2、偏向锁 -> 轻量级锁
偏向锁指只有一个线程访问synchronized锁,该线程不需要使用同步操作就可以访问对象。
在偏向锁下,如果其他线程访问该对象,会先检查该对象的偏向锁表示,如果和自己的线程ID想通,则直接获取锁。如果不同,则该对象的锁状态就会升级到轻量级锁状态。
3、轻量级锁 -> 重量级锁
在轻量级锁状态中,JVM为对象头中的MarkWord预留了一部分空间,用于存储指向线程栈中锁记录的指针。
当一个线程访问该对象时,JVM会将对象头中的MarkWord复制一份到线程栈中,并在对象头中存储线程栈中的指针。此时,如果另一个线程想要访问该对象,会发现该对象已经处于轻量级锁状态,并尝试使用CAS操作将对象头中的指针替换成自己的指针。
如果替换成功,则该线程获取锁成功;如果失败,则表示已经有其他线程获取了锁,则该锁状态就会升级到重量级锁状态。
4、重量级锁
当锁状态升级到重量级锁状态时,JVM会将该对象的锁变成重量级锁,并在对象头中记录指向等待队列的指针。
此时,如果一个线程想要获取该对象的锁,则需要先进入等待队列(而不是去直接尝试获取锁),等待该锁被释放。当锁被释放时,JVM会从等待队列中选择一个线程唤醒,并将该线程的状态设置为就绪状态,然后等待该线程重新获取该对象的锁。