synchronized的实现原理(锁升级过程)

1.1 实现基础

 synchronized实现同步的基础:java中的每一个对象都可以作为锁(表现为3种形式)。

  • 对于普通同步方法,锁是当前实例对象(this指针)
  • 对于静态同步方法,锁是当前类的Class对象(xxx.class)
  • 对于同步方法块,锁是synchronized括号里指定的对象(synchronized(obj){})

1.2 对象头

synchronized使用的锁对象是存储在Java对象头里的。

 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:

  • 对象头
  • 实例数据
  • 对齐填充

 非数组对象头分为两部分,数组对象分为三个部分(每个部分占1个字宽):

  • 第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持久的锁、偏向线程的ID等。官方称为Mark Word
  • 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  • 如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据。
    对象头
     注意:无锁状态时是JVM默认状态的对象头结构,在运行期间,对象头结构会根据锁的不同发生下列结构变化

1.3 实现原理

 JVM规范中描述了synchronized实现原理:JVM基于进入和退出monitor对象来实现方法的同步和代码块同步,但两者的实现细节不一样。

  • 代码块同步是使用monitorenter和monitorexit指令实现的
    • monitorenter指令在编译后插入到同步代码块的开始位置
    • monitorexit指令插入到方法结束处和异常处
    • 两个指令必须成对出现一一对应
  • 方法同步使用另一种方式,JVM规范中没有细说,但是它同样可以使用这两个指令实现
// 通过编译.class文件进行对比可以发现,同步代码块是使用monitorenter和monitorexit指令实现,
// 而同步方法是在方法标记位上添加ACC_SYNCHRONIZED来实现
public class TestSynchronized {
    public static void main(String[] args){
        test01();
    }

    public static void test01(){
        synchronized (TestSynchronized.class){
            System.out.println("test method");
        }
    }
    public synchronized static void test02(){
        System.out.println("test method");
    }
}

同步代码块
同步方法
 任何对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,会尝试获取对象锁对应的monitor的所有权,即尝试获得对象的锁

1.4 锁的升级与对比

 Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁轻量级锁。在1.6中,锁一共有4中状态(由低到高,锁可以升级但不能降级):

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态
1.偏向锁

 偏向锁意义:据调查,大多数情况下,锁总是被同一线程调用,所以就出现了偏向锁。

 偏向锁总是偏向于第一次获取锁的线程,一般无竞争状态时使用的就是偏向锁,可以使用-XX:+/-UseBiasedLocking参数启用/关闭偏向锁,默认是开启的(线程数多的时候,适合关闭偏向锁,因为竞争激烈,锁会升级,关闭后可以省略升级过程,直接使用轻量级锁)。

偏向锁加锁过程:

  1. 执行到monitorenter时,首先检查所标志是否为01(01表示未上锁)
  2. 其次检查偏向锁位是否为0(0表示无锁,1表示当前已是偏向锁)
  3. 如果检查成功,通过CAS操作,将Mark Word中的线程ID设置为自己的线程ID,然后将偏向锁位设置为1
  4. 如果第二次仍是该线程进入同步区,那么不再需要执行第三步
  5. 如果第二次是其他线程进入,那么锁升级

 偏向锁升级为轻量级锁:

  1. 竞争激烈,多个线程同时争锁,锁升级为轻量级锁
  2. 第一个线程进入后设置为偏向锁,第二次再来的线程不是该线程而是其他线程,锁升级为轻量级锁

关闭偏向锁

 偏向锁是默认启用的,它会在程序启动几秒后才激活,可以通过JVM参数:-XX:BiasedLockingStartupDelay=0关闭延迟。也可以使用参数:-XX:UserBiasedLocking=false关闭偏向锁

2.轻量级锁

在这里插入图片描述
加锁过程:

  1. 当某个线程想要占用这把锁的时候,它会首先在自己的栈(帧)中创建一个锁记录(LockRecord)
  2. 然后将Mark Word信息复制(hashCode、分代年龄等)到LockRecord中
  3. 用CAS操作将Mark Word替换掉LR指针,即将Mark Word中除了标志位外的部分替换成一个指针,这个指针指向自己的LockRecord
  4. 哪个线程替换成功,哪个线程就成功得到了锁

 轻量级锁自旋次数超过10(任意一个线程),或者正在自旋的线程超过CPU核数一半,那么轻量级锁升级为重量级锁,可以通过-XX:+PreBlockSpin指定自旋次数

3.重量级锁

 因为需要阻塞,并且需要向操作系统级别申请,所以说是重量级。重量级锁是操作系统来决定由哪个线程来获得锁的:

  1. 生成或者复用monitor对象
  2. Mark Word不再指向LR,而是指向monitor对象
  3. 线程阻塞,进入_EntryList排队,由操作系统决定唤醒谁
  4. 操作系统唤醒线程后该线程的Mark Word替换为monitor pointer,然后执行
monitor对象

 monitor可以理解为一个操作系统级别的互斥变量,当一个线程想要执行一段被synchronized圈起来的同步方法或者代码块时,该线程得先获取到synchronized修饰的对象对应的monitor。

 在hotSpot虚拟机中,monitor是由ObjectMonitor实现的。其源码是用c++来实现的

 Java代码里不会显示地去创造这么一个monitor对象,我们也无需创建,事实上可以这么理解:我们是通过synchronized修饰符告诉JVM需要为我们的某个对象创建关联的monitor对象。

推荐一篇关于锁只是的博客,原文链接 :https://blog.csdn.net/lengxiao1993/article/details/81568130

  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值