首先看一个简单的代码
public class SynchronizedDemo {
public static void main(String[] args) {
synchronized (SynchronizedDemo.class) {
}
method();
}
private static void method() {
}
}
通过 javap -v SynchronizedDemo.class 命令查看class文件
看到了3、5行的命令 monitor
这是因为执行同步方法和同步代码块是,首先要获得该对象的锁也就是该对象的监视器monitor才能进入,如果没有获得该对象的监视器进入BLOCKED阻塞状态;
以上可以看出获得对象的监视器(monitor)就可以获得对象锁,那么这个对象锁到底怎么理解呢 ,这里引入了对象头的概念 , 这里的对象锁相当于一个标记,获取到对象锁后将对象锁存放到对象头的markword中,具体的markword可以另外了解下
mark word 中存储了 锁的状态(锁的状态下面会说),获得该锁的线程ID等;
总结:
synchronize获取到对象锁就是获取到对象的monitor,进入对象同步块,将锁的标志信息存入到该对象头的mark word中
锁的四种状态
- 无锁状态
- 偏向锁
- 轻量级锁
- 重量级锁
偏向锁
大多数情况下,锁可能都由一个线程锁获得,为了让线程获取锁的代价更小引入了偏向锁:
首先获取锁需要获取到对象的monitor(监听器);获取到对象后将偏向锁的ID存入到对象头中的MARK word中和栈帧锁记录当中;在以后获取锁时,不需要通过CAS竞争锁,只需通过对象头中的偏向锁ID 指向当前线程的偏向锁
再次获取锁的几种情况:
1.id匹配直接获取锁;
2.匹配失败,如果锁状态为无锁状态;通过cas竞争获取锁
3.如果锁状态为偏向锁状态,有第二个线程访问这个对象出现线程竞争;(偏向锁不会主动释放锁)操作系统检测原来只有该线程的锁状态,
如果不存活;修改为无锁状态,重新指向新的线程;
如果存活,检测该对象的使用情况,
如果仍然需要持有该偏向锁,则该偏向锁升级为轻量级锁(锁升级);不需要保持偏向锁,让对象恢复为无锁状态,进行重新偏向
轻量级锁
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁
锁再次升级:b在线程竞争时,发现线程已经让a占用,b线程自旋,等待a线程释放锁;当自旋结束后,发现a线程还是没有释放或是c线程占用。则b线程试图将轻量级锁升级为重量级;
重量级
轻量级锁膨胀之后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
synchronized的执行过程:
- 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
- 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
- 如果自旋成功则依然处于轻量级状态。
- 如果自旋失败,则升级为重量级锁。