synchronized锁升级过程

希望的天 等级 518 0 0

1.前置知识:

1.1 JAVA对象的内存布局

        hotspot虚拟机中,普通对象在堆中的存储可以划分成三部分:对象头(包含了MarkWord和类型指针)、实例例数据和padding。

synchronized锁升级过程

JAVA对象的内存布局

MarkWord的长度为4byte/8byte,用于存储对象自身的运行时数据,如HashCode、GC分代年龄、是否为偏向锁、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、Monitor等。最后2位用于存储状态信息。

synchronized锁升级过程

MarkWord不同状态下的存储结构

对于锁而言,重点关注的有两点: biased_lock和状态信息。

1.2.Monitor/ LockRecord

在重量级锁时,MarkWord中会存储指向Monitor的指针。轻量级锁加锁时,会尝试将对象的MarkWord CAS地更新到线程的栈帧中。这部分作了解,后面细说。

2. 锁升级过程

锁升级状态的四个阶段: 无锁、偏向锁、轻量级锁和重量级锁。

2.1 无锁: //TODO

2.2 偏向锁:

意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。若虚拟机启用了偏向锁,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。一旦出现另外一个线程去尝试获取这个锁的情况,偏向模式就马上宣告结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”),撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就按照轻量级锁那样去执行。

问题来了:1.对象头中原来存了哈希码,现在哈希码没有了,咋整?Easy, 哈希码由Object::hashCode()生成,返回对象的一致性哈希码,因此只要对象是同一个且没有重写hashCode方法,重新生成的哈希码也不变。

2.3 轻量级锁:

轻量级锁是相对于重量级锁而言的,它设计的初衷实在没有多线程竞争的前提下,减少重量级锁的性能损耗。

2.3.1 轻量级锁加锁的实现方式

在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁状态位是01),虚拟机在当前线程的栈帧中创建lockRecord, 用于存储对象MarkWord的拷贝(加锁成功后存储markword)和对象的引用地址(用于锁住之后完成对象的访问定位)。

synchronized锁升级过程

虚拟机在线程thread0的栈帧中创建了LockRecord

创建完LockRecord之后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向当前线程中LockRecord的指针。这里CAS的比较方法是:锁标志位是否为01,如果是则更新为LockRecord地址并将标志位置位00。

synchronized锁升级过程

尝试将MarkWord CAS地更新为指向LockRecord的指针

如果更新成功,此时LockRecord中存放了对象的原来的markword信息,同时将对象的markword锁标志位置为00,而对象的markword则存放了持有锁的线程的LockRecord地址,如下图。如果更新失败,则表示该对象的锁已经被持有了,持有锁的线程可能是他自己,也可能是其他线程。然后虚拟机先检查对象的MarkWord是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了;否则就说明这个锁对象已经被其他线程抢占了,当前线程开始不断自旋重试。

synchronized锁升级过程

更新成功后轻量级锁状态下Markword

synchronized锁升级过程

CAS更新成功后线程栈帧和被锁住的对象

为什么更新失败后仍要检查对象MarkWord是否指向当前栈帧呢?原因是锁的重入。CAS更新失败有两种可能,1.它自己已经持有了该对象的锁,现在要重入。 2.其他线程持有了对象的锁。若是当前线程CAS更新了MarkWord,那么当前线程再次想要持有对象的锁时,它应该要能重入。锁重入的时候,又创建了新的LockRecord,但由于CAS更新失败,它内部并没有对象原来MarkWord的拷贝。

synchronized锁升级过程

锁重入

轻量级锁状态下,MarkWord指向哪个线程的栈帧,就意味着哪个线程持有了锁。

2.3.2 轻量级锁的解锁

当退出synchronized(obj)代码块的时候,若发现有取值为null的锁记录,表示有重入。此时解锁的操作就是移除这个记录,重入次数减一(见上图)

如果锁记录的MarkWord拷贝不为null, 则需要CAS将MarkWord恢复回对象头。如果恢复成功,则表示成功解锁。恢复失败,则表示已升级为重量级锁,进入重量级锁的解锁流程。

2.3.3锁膨胀

当并发高,线程之间竞争激烈的时候,采用CAS自旋的方式会有问题,没获取到锁的线程长时间占用着CPU,却又没能拿到锁。时间一长,系统中自旋的线程太多,看起来cpu一直在忙,任务进度却非常缓慢。 因此自旋应当有一定的次数限制,超过次数就进入锁膨胀流程,将锁升级为重量级锁,使拿不到锁的线程进入阻塞状态。

升级过程如图:thread1自旋获取轻量级锁失败,先为obj对象创建重量级锁Monitor,Monitor的owner指向当前持有锁的线程t0;然后将指向LockRecord的指针更改为指向Monitor的指针,将锁的状态改为10; 最后让自己进入阻塞队列中。

synchronized锁升级过程

升级为重量级锁

2.4重量级锁

2.4.1 Monitor结构及重量级锁上锁

当对象的锁为重量级锁的时候,MarkWord存放了指向Monitor的指针,这个Monitor实际上就是对象的锁信息。它包含了:持有锁的线程,想要持有锁但被阻塞的队列EntryList以及处于waiting状态的线程。这也就不难理解为什么使用了obj.wait()的时候,会直接升级到重量级锁,因为其他状态的下没有waitSet啊,那我在那里等着被唤醒嘛。

synchronized锁升级过程

Monitor结构

Monitor对象被加锁的共享变量关联,在Monitor对象中记录锁的持有锁的线程,并在对象内部维护了等待持有锁的阻塞队列EntryList,若thread1执行到synchronized(obj)时,obj的锁已被其他线程获取,那么t1就进入阻塞状态,并进入阻塞队列。

synchronized锁升级过程

线程1获取锁失败,进入阻塞队列这里有个问题:

这里有个问题,当线程1进来时,线程2正在准备持有当前monitor,但是t2又还没持有monitor,应该怎么处理呢。(我猜感觉可能大概是cas的方式?进来先判断是否owner已经不为null了,如果是则直接进入entryList,不是则cas地比较并交换,如果比较失败,则下一轮重新判断? 有懂的大神可以评论区解答一下哈)

2.4.2 重量级锁解锁

当持有锁的线程执行完synchronized(obj)中的代码块时,释放锁。唤醒EntryList队列中的所有线程,然后这些线程开始抢占锁,抢到了就成为owner,未抢到则回到阻塞队列中。

3.锁升级过程

一开始是无锁状态,当有线程使用的时候会升级成偏向锁,这时候是单线程状态,一旦有第二个线程竞争锁,将会升级为轻量级锁,其余线程会自旋等待,当自旋到一定次数时升级成重量级锁,这时其余线程进入等待队列,等待被唤醒。另一种升级成重量级锁的方式是,遇到wait()等待其他人notify(),会自动直接升级成重量级锁。

盗用一张图:http://www.jetchen.cn/synchronized-status/

synchronized锁升级过程

本文转自 https://www.jianshu.com/p/5917486df9cc,如有侵权,请联系删除。

收藏
评论区

相关推荐

使用synchronized关键字封装一个锁
代码如下: public class Lock { private boolean isLocked false; public void lock() { synchronized (this) { while (isLocked) { try {
JVM--指令重排序+volatile关键字
volatile 关键字 1、 volatile 翻译为 不稳定的,容易改变的。意思很明确,如果使用volatile 定义一个变量,意思就是可能该变量改变频繁,并且设计到多线程访问问题。 2、不过 现在jdk 的synchronized关键字 性能已经足够出色,也提供了多种Lock 类,因此 volatile关键字能实现的功能 jdk 的同步方法都能够实
3 Java对象的内存布局以及对象的访问定位
先来看看Java对象在内存中的布局 一 Java对象的内存布局 在HotSpot虚拟机中,对象在内存中的布局分为3个区域 对象头(Header) Mark Word(在32bit和64bit虚拟机上长度分别为32bit和64bit)存储对象自身的运行时数据,包括哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时 间戳等 类型指
android 面试题总结
Java部分 一、多线程   Join()  线程加入,执行此方法的线程优先使用cpu   Yeild()  线程释放资源使所有线程能有相等的机会使用cpu   Sleep() 相当于让线程睡眠,交出CPU,让CPU去执行其他的任务(不会释放锁)。 Wait()方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
一篇文章弄懂Java多线程基础和Java内存模型
文章目录 一、多线程的生命周期及五种基本状态 二、Java多线程的创建及启动 1.继承Thread类,重写该类的run()方法 2.通过实现Runnable接口创建线程类 3.通过Callable和Future接口创建线程 三、Java内存模型概念 四、内存间的交互操作 五、volatile和synchronized的
synchronized锁升级过程
1.前置知识:    1.1 JAVA对象的内存布局            hotspot虚拟机中,普通对象在堆中的存储可以划分成三部分:对象头(包含了MarkWord和类型指针)、实例例数据和padding。JAVA对象的内存布局MarkWord的长度为4byte/8byte,用于存储对象自身的运行时数据
Zookeeper分布式锁?
客户端A要获取分布式锁的时候首先到locker下创建一个临时顺序节点(node_n),然后立即获取locker下的所有(一级)子节点。此时因为会有多个客户端同一时间争取锁,因此locker下的子节点数量就会大于1。对于顺序节点,特点是节点名称后面自动有一个数字编号,先创建的节点数字编号小于后创建的,因此可以将子节点按照节点名称后缀的数字顺序从小到大排序,这样
操作系统学习笔记 原来自旋锁还可以这样实现!
在java中我们知道对线程使用CAS(compare and swap)来实现自旋锁,在没有学习操作系统之前,我以为这是唯一一种方法了。但是今天学到了操作系统中的同步互斥,终于明白了原来CAS、TS、Swap这些都是硬件提供的原子操作罢了!不仅CAS可以实现自旋锁,TS(Test and Set)同样可以! 临界区 临界区进程中访问临界资源的一段需要互斥执行
阿里一线架构师技术图谱!十年开发经验Java架构师
开头我们面试的时候 ,经常会被问这种到问题:Spring中bean的循环依赖怎么解决? Spring中bean的加载过程? spring相关的问题一直是大厂面试常问到的一个问题,也是一直困扰这我们,不知道从哪里下手,今天举例分析大厂的一些spring相关的面试真题。和分享我学习spring相关问题所整理的一些知识点。 01 并发宝典:面试专题面试专题分为四个
推荐程序员面试秘籍!2021年大厂Java岗面试必问
01 JAVA基础 1.1 java知识点 Hashmap 源码级掌握,扩容,红黑树,最小树化容量,hash冲突解决,有些面试官会提出发自灵魂的审问,比如为什么是红黑树,别的树不可以吗;为什么8的时候树化,4不可以吗,等等 concureentHashMap,段锁,如何分段,和hashmap在hash上的区别,性能,等等 HashTable ,同步锁,这块可
阿里巴巴技术专家之作,吊打面试官系列!
美团 一面: 1、ConcurrentHashMap实现原理 2、HashMap实现原理 3、锁的实现原理 4、synchronized和重入所实现原理以及区别 5、一个char[]数组,里面有空格,以&结束。 6、jvm内存模型,都存什么。以及垃圾回收算法,垃圾回收器。 7、内存溢出的场景 8、设计模式,以及自己使用的场景。 9、Sping的AOP实现原
面试百度和美团,竟然问我多线程安全问题,正好撞在我知识点上
解决多线程安全问题无非两个方法synchronized和lock 具体原理以及如何 获取锁AQS算法本篇文章主要讲了lock的原理 就是AQS算法,还有个姊妹篇 讲解synchronized的实现原理 也是阿里经常问的,一定要看后面的文章,先说结论:非公平锁tryAcquire的流程是:检查state字段,若为0,表示锁未被占用,那么尝试占用,若不为0,检查
AtomicStampedReference是怎样解决CAS的ABA问题
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。 什么是ABA问题但凡对Java有一点深入就会知道 CAS,即 compareAndSwap。在Java中使用 Unsafe 类提供的native方法可以直接操作内存,其中就有对compareAndSwap的实现。javapublic final nati
简简单单复习一哈HashMap
HashMap可被序列化,线程不安全,允许null值和null键,安全的MapCollections.synchronizedMap(): / Returns a synchronized (threadsafe) map backed by the specified map. In order to guarantee s
腾讯java社招面试流程,附大厂真题面经
拼多多一面首先自我介绍参加过哪些项目并发编程三要素?实现可见性的方法有哪些?多线程的价值?创建线程的三种方式的对比?画出线程的状态流转图常用的并发工具类有哪些?CyclicBarrier 和 CountDownLatch 的区别CAS 的问题:1、CAS 容易造成 ABA 问题2、不能保证代码块的原子性3、CAS 造成 CPU 利用率增加ReadWriteL