JUC-Synchronized

2. Synchronized的实现原理与应用

2.1 Synchronized 的几种表现形式

java中的任何一个对象都可以作为锁:

普通同步方法 (锁是当前实例对象)

​ 该实例对象被多个线程使用,只能有一个线程访问对象的一个synchronized同步方法或者同步代码块; 但是可以访问非synchronized的代码;不同实例可以各自访问自己实例中的同步方法/代码块。

public synchronized void func(){
    System.out.println("test synchronized");
}

静态同步方法(锁是当前类的Class对象)

​ Synchronized的锁作用域是一个"类",可以防止多个线程同时访问这个类中的synchronized方法,对所有的实例对象起作用。

public static synchronized void func(){
	System.out.println("test synchronized");
}

同步代码块(锁是Synchronized括号里配置的对象)

public void func(){
    synchronized(...){
          System.out.println("test synchronized");
    }
}

2.2 Synchronized 的实现原理

​ 在JVM规范中,Synchronized是基于进出Monitor对象来实现方法同步和代码同步,每一个对象都有一个对应的 monitor 与之关联,同步方法块和同步代码块实现原理有所不同,但是都可以通过一下两个指令来实现:

​ monitorenter 指令,是代码编译后插入到同步代码的开始处,尝试获取对象的 monitor 的所有权,也就是尝试获取对象的锁,monitor会处于锁定状态;

​ monitorexit 指令,是代码编译后插入到同步代码的结束或者异常处, 释放对象的 monitor 的所有权,也就是释放锁。

2.3 对象头信息

​ Synchronized用的锁信息是存储在对象头中;

长度内容说明
32/64bitMark Word存储对象的hashCode或锁信息
32/64bitClass Metadata Address存储对象类型数据的指针
32/64bitArray length如果对象是数组,此处存储数组长度

在这里插入图片描述

对象头中的MarkWord

在这里插入图片描述

2.4 Synchronized 锁升级

为什么有锁升级的过程?

​ 早期Java中,Synchronized属于重量级锁,是依赖于操作系统的Mutex Lock(系统互斥信号量)来实现的,挂起恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要OS切换CPU状态来完成。Java6 之后为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。

什么是内核态与用户态的转换?

​ 这种切换会消耗大量资源,因为用户态和内核态都各自有各自专用的内存空间,寄存器等,用户态切换至内核态需要传递许多变量,参数给内核,内核也需要保护好用户态在切换时,一些寄存器值,变量等。以便内核态调用结束后切换回用户态。

2.4.1 偏向锁

为什么会出现偏向锁?

​ HotSpot作者研究发现,大多数情况下,锁不仅存在多线程竞争,还存在同一个线程多次获得锁,为了让线程获得锁的代价更低,引入了偏向锁。

偏向锁的机制:

​ 当一个线程访问同步块并获取锁时,会在对象头,和栈桢中的锁记录里存储锁偏向的线程ID(表示该线程已经获得了锁),以后该线程在进入和退出同步块的时候,不需要做CAS自旋,只需要对比一下对象头中的Mark Word是否存储着指向当前线程的偏向锁。

​ 如果相等,表示该线程已经获得了锁。

​ 如果不等,查看Mark Word中偏向锁标志位是否为1

​ 如果没有设置,表示当前对象还没有被其他线程偏向锁定,则使用CAS竞争锁,竞争成功后线程ID指向当前线程,标志位设位1;

​ 如果有设置,表示为偏向锁,尝试使用CAS将对象头偏向锁指向当前线程;

​ 如果竞争成功:表示之前获得偏向锁的线程不处于存活状态,偏向锁线程Id指向新的线程,锁不升级

​ 如果竞争失败:表示之前获得偏向锁的线程处于存活状态,发生了偏向锁竞争,此时偏向锁升级为轻量锁,保证线程之间的公平竞争锁。

偏向锁的撤销:

​ 偏向锁使用一种等到竞争出现才会释放所的机制,线程是不会主动释放偏向锁的。

​ 当发生竞争的时候,偏向锁的撤销需要等待全局安全点(这个时间点上没有正在执行的字节码)。它会首先暂停持有偏向锁的线程,等待该线程进入安全点(SafePoint),然后通过CAS操作尝试将对象头的偏向线程ID置为0,标志着偏向锁的撤销。然后检查持有偏向锁线程是否存活,

存活偏向锁升级为轻量锁。

不存活,则将对象头设置为无锁状态,其他线程将使用CAS竞争偏向锁。

偏向锁的撤销是一个比较消耗性能的操作,因为它涉及到了线程间的同步和状态的转换。

2.4.2 轻量锁

轻量级锁的加锁

​ 当偏向锁升级为轻量级锁,线程执行同步块之前,JVM先会为当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 MarkWord (对象的HashCode) 复制到栈桢锁记录上,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word 替换为 “ 指向栈中锁记录的指针 ”,如果成功,表示获取锁;如果失败表示其他线程竞争锁,当前线程使用CAS自旋获取锁,自旋到了阈值,表示获取锁失败,此时轻量级锁升级为重锁。

在这里插入图片描述

code 1 :判断对象是否是无锁状态(低三位 = 001),如果是,执行code 2,如果不是,执行code 4。

code 2:在栈中建立一个Lock Record,将无锁状态的Mark Word拷贝到锁记录的Displaced Mark Word中,将owner指向当前对象。

code 3:尝试通过CAS 将锁对象的 Mark Word 更新为指向Lock Record的指针,如果更新成功,该线程获取到轻量级锁,并且需要把对象头的Mark Word的低两位改成10(注意这里修改的是对象头的Mark Word,Lock Record中记录的还是无锁状态的Mark Word);如果更新失败,执行code 4。

code 4:对象是轻量级锁定状态,判断对象头的 Mark Word是否指向当前线程的栈帧。如果是,则这次为锁重入,将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录重入,该线程重入轻量级锁。如果不是,执行code 5。

code 5:线程获取轻量级锁失败,锁膨胀为重量级锁,对象头的Mark Word改为指向重量级锁monitor的指针。获取失败的线程不会立即阻塞,先适应性自旋,尝试获取锁。到达临界值后,阻塞该线程,直到被唤醒。

轻量级锁的解锁

​ 轻量级锁解锁的时候,使用CAS将栈桢中锁记录信息(此时为对象的HashCode)与对象头的Mark Word(此时为指向栈中锁记录的指针)进行交换,交换成功,表示释放所,交换失败(表示当前锁存在竞争)。

在这里插入图片描述

code 1 :检索当前线程栈中的锁记录空间,从低位往高位找到第一条和此对象有关的Lock Record。加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null,相应的,在解锁时需要判断Displaced Mark Word是否为 null,如果是,则说明是锁重入解锁,移除onwer的指向,不做替换操作;如果不是,执行code 2。

code 2:通过CAS把当前线程栈帧Lock Record中的Displaced Mark Word替换到对象头的Mark Word中去,如果替换成功,则轻量级解锁成功;如果替换失败,则说明发生了锁膨胀,对象现在是重量级锁定状态,执行code 3。

code 3:执行重量级锁释放流程,释放重量级锁,同时唤醒被阻塞的线程去获取锁。

轻量级锁的适用场景
少量的线程竞争锁,且所有者线程占用锁的事件补偿,追求响应速度的场景。

什么时候会升级为轻量级锁
当对象的偏向模式被关闭、对象处于已偏向已锁定、已偏向未锁定但不支持重偏向的场景下,就会升级为轻量级锁。

什么时候会升级为重量级锁
当竞争产生时就会升级为重量级锁,比如,两个线程同时获取锁,成功的线程会获取到轻量级锁,失败的线程会执行锁膨胀,升级为重量级锁。

轻量级锁怎样实现锁重入
当轻量级锁已经被线程持有,且对象头的Mark Word指向的是当前线程的栈帧时,会把本条Lock Record的Displaced Mark Word 设置为 null,实现锁重入。当重入解锁时,只需要修改所有者onwer的指向。

轻量级锁是否会自旋
轻量级锁流程不会自旋,自旋发生在产生竞争后,获取失败的线程将锁膨胀为重量级锁。失败的线程不会立刻阻塞,而是先尝试适应性自旋,等待所有者释放锁,当到达临界值后再阻塞。

2.4.3 重锁

在这里插入图片描述

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞行模式、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值