synchronized 学习,偏向锁,轻量级锁,重量级锁介绍
Synchronized
1 加锁方式
- 修饰实例方法 -- > 锁的是当前实例
- 修饰静态方法 -- > 锁的是当前类 例如Demo.class
- 修饰代码块 -- > 看具体锁对象分析
2 锁的存储
private static Object obj = new Object();
public void add() {
synchronized (obj){
count++;
}
}
//分析,synchronized(lock)是基于lock这个对象的生命周期来控制粒度的,所以所得存储应该和lock对象有关,探究jvm源码得知,当某个对象被synchronized当成同步锁时,那么围绕这个锁的一系列操作都和Mark Word有关系。
- 以32位虚拟机为例
为什么任何对象都可以实现锁?
每个Object在jvm都有一个native的C++对象进行对应,线程在获取锁的时候,实际上就是获得一个monitor(监视器)对象,monitor可以认为是一个同步对象,所有的java对象天生都携带monitor,多个线程访问同步代码块时,其实相当于去争抢监视器对象并修改对象中的锁标记
3 锁升级
3.1 偏向锁的获取和撤销
hotspot虚拟机的作者调查发现,大部分情况下,加锁的程序不仅仅不存在线程竞争,而且总是由同一个线程获得锁,基于这样一个概率,synchronized在jdk1.6之后做了一些优化,引入了偏向锁和轻量级锁的概念
- 基本原理
在线程访问同步代码块的时候,会在对象头中存储当前线程的ID,后续这个线程进入和退出这个代码块的时候,不需要再次加锁和释放锁,而是直接比较对象头里面是否存储了指向当前线程的偏向锁,如果相等表示锁是偏向当前线程的,没必要再次进行获得锁
- 执行步骤
-
偏向锁的撤销
偏向锁不存在撤销,如果cas失败,则会升级轻量级锁
3.2 轻量级锁
- 基本原理
偏向锁升级为轻量级锁时,对象的markword也会进行相应的变化,升级过程如下
(1) 线程在自己的栈桢中创建锁记录 LockRecord
(2) 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中
(3) 将锁记录中的 Owner 指针指向锁对象
(4) 将锁对象的对象头的 MarkWord替换为指向锁记录的指针。
- 执行步骤
轻量级锁加锁过程中用到了自旋,默认自旋10次,可以通过 PreBlockSpin 修改, jdk1.6之后引入了自适应自旋锁
3.3 重量级锁
当轻量级锁膨胀为重量级锁后,意味着线程只能被挂起阻塞来等待被唤醒了
任意线程对 Object(Object 由 synchronized 保护)的访问,首先要获得 Object 的监视器。如果获取失败,线程进入同步队列,线程状态变为 BLOCKED。当访问 Object 的前驱(获得了锁的线程)释放了锁,然后会唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。
- 执行步骤