synchronized(偏向锁和轻量级锁)(TODO)

synchronized

  JDK1.6对synchronized进行了各种优化,性能已经和ReentrantLock差不多了。

  Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

  JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。

  代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。

  但是,方法的同步同样可以使用这两个指令来实现。

  

Java对象头

  如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit。

                          Java对象头的长度

  Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。

                          Mark Word的存储结构

                          Mark Word的状态变化

  在64位虚拟机下,Mark Word是64bit大小

                          64bit的Mark Word的存储结构

锁升级与对比

  JDK1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。

  锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级,锁可以升级但不能降级。

  

偏向锁

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出
同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否
存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需
要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则
使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

  偏向锁撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,
持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正
在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,
如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈
会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他
线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

                  偏向锁初始化

  书上说了一大堆,简单来说就是分两种场景,一种是有竞争,一种是无竞争

  无竞争:

  无竞争的场景可以假设是一个单线程请求synchronized锁,当第一次请求锁时,锁的对象头中的偏向锁线程ID是空,状态是0。

  锁请求成功后,JVM会将这个线程ID写入对象头,偏向锁线程ID不为空,状态是1,然后该线程执行完同步代码后释放锁,接着又继续请求获取锁。

  然后判断锁的请求头中的偏向锁状态是否为1,为1则再判断偏向锁的线程ID是否是该线程ID,如果是则直接进入临界区执行同步代码,无需加锁和解锁的操作。

  有竞争:

  有竞争的场景的第一个请求锁的线程同无竞争场景一样,假设线程A请求锁成功,设置偏向锁状态为1,记录偏向锁线程ID,但由于存在锁竞争,第二个线程B请求锁时,发现偏向锁状态是1,

  但是锁的对象头中偏向锁记录的线程ID并不是自己,这时就需要撤销偏向锁,撤销之前必须要检查线程A是不是还活着,因为此时线程A可能正在临界区内执行同步代码,也可能线程A活的很滋润,

  在另外一把锁的临界区内执行同步代码,也可能代码执行完毕已经释放锁然后线程池中等待复用,也可能代码执行完毕且线程已经死亡。

  所以线程B请求锁时发现偏向锁的线程ID不是自己,JVM会暂停对象头中记录的偏向锁ID。

  如果线程A暂停失败说明线程死亡,线程B直接将对象头设置成无锁状态

如果线程A暂停成功说明线程A活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,

要么锁状态恢复到无锁(锁不升级)或者标记锁对象不适合作为偏向锁(升级为轻量级锁),最后唤醒暂停的线程。

这里有个疑惑,TODO 一下,什么情况锁不升级,什么情况锁才升级?

  我的想法是,由于锁只能升级不能降级,那么盲目升级锁势必会增大锁开销而降低性能。所以线程B发现锁的偏向线程ID不是自己还需要一些判断再决定是否需要升级锁。

  即如果线程A活着,但是线程A处于挂起状态或者当前线程A在活动中且栈信息中的锁信息不是线程B请求的锁,则锁状态置位无效,不升级锁

  如果线程A正在锁的临界区执行同步代码,则需要锁升级,升级为轻量级锁。

  

  所以总结下来,偏向锁请求过程如下:(这里只说偏向锁的请求,即所标志为=01的情况)

  判断锁对象的对象头中的Mark Word是否为无锁状态(锁状态=0)

    a.是,CAS设置锁状态为1,偏向锁ID为当前线程ID

    b.否:判断锁状态是否为1

        a.是,继续判断偏向锁ID是否为当前请求线程ID

          a.是,无需加锁和解锁,直接进入临界区执行同步代码

          b.否,判断偏向锁ID对应的线程是否存活

            a.是,暂停线程,判断线程栈中的锁对象信息是否为当前请求的锁对象信息

              a.是,存在竞争,当前线程请求的锁已被其他线程占用且正在临界区执行同步代码,锁升级为轻量级锁,在暂停的线程栈中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到                 锁记录中,使用CAS将对象头中的Mark Word替换为指向锁记录的指针,恢复之前暂停的线程

              b.否,没有线程在当前锁对象的临界区内,修改锁的偏向线程ID为当前线程或重置为无锁状态,CAS设置锁状态为1,偏向锁ID为当前线程ID。恢复之前暂停的线程

        b.否,锁已升级,不使用偏向锁

轻量级锁

  1)加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并
将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用
CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失
败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

2)解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成
功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

                    锁膨胀流程

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级
成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,
都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮
的夺锁之争。

总结轻量级锁请求过程:

  判断锁对象的对象头中的Mark Word是否为无锁状态(锁状态=0)

    a.是,在栈帧中创建锁记录空间,复制锁对象头的Mark Word到锁记录中,CAS将锁对象的对象头中的Mark Word替换为指向锁记录的指针。

      a.替换成功,则加锁成功,执行同步代码,执行完毕后CAS操作将Displaced Mark Word替换回到对象头进行解锁

        a.解锁成功,此时锁状态为无锁状态

        b.解锁失败,说明当前线程在执行同步代码的时候有其他线程来请求锁资源,且请求失败将锁升级为重量级锁

      b.替换失败

        自旋,走下面的自旋流程

    b.否,检查对象的Mark Word是否指向当前线程的栈帧的锁记录

      a.是,说明当前线程已持有锁,直接进入同步代码

    b.否,自旋,空循环自旋一段时间

      a.自旋成功,获取锁成功

      b.自旋失败,升级锁为重量级锁,修改锁对象的Mark Word指针指向重量级锁

  

锁的优缺点对比

参考:

《Java编发变成艺术》

原文地址:https://www.cnblogs.com/simple-ly/p/11147350.html

时间: 2024-08-01 07:40:12

synchronized(偏向锁和轻量级锁)(TODO)的相关文章

Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) 一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的.而操作系统实现线程之间的切换这就需要从用户态转换到核心

java 偏向锁、轻量级锁及重量级锁synchronized原理

Java对象头与Monitor java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的. 对象头包含两部分:Mark Word 和 Class Metadata Address 其中Mark Word在默认情况下存储着对象的HashCode.分代年龄.锁标记位等以下是32位JVM的Mark Word默认存储结构 由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成

java 中的锁 -- 偏向锁、轻量级锁、重量级锁

理解锁的基础知识 如果想要透彻的理解java锁的来龙去脉,需要先了解以下基础知识. 基础知识之一:锁的类型 锁从宏观上分类,分为悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作. java中的乐观锁基本都是通过CAS操作实现的,CAS

多线程之:偏向锁,轻量级锁,重量级锁

一:java多线程互斥,和java多线程引入偏向锁和轻量级锁的原因? --->synchronized的重量级别的锁,就是在线程运行到该代码块的时候,让程序的运行级别从用户态切换到内核态,把所有的线程挂起,让cpu通过操作系统指令,去调度多线程之间,谁执行代码块,谁进入阻塞状态.这样会频繁出现程序运行状态的切换,线程的挂起和唤醒,这样就会大量消耗资源,程序运行的效率低下.为了提高效率,jvm的开发人员,引入了偏向锁,和轻量级锁,尽量让多线程访问公共资源的时候,不进行程序运行状态的切换,由用户态进

Java锁---偏向锁、轻量级锁、自旋锁、重量级锁

之前做过一个测试,反复执行过多次,发现结果是一样的: 1. 单线程下synchronized效率最高(当时感觉它的效率应该是最差才对): 2. AtomicInteger效率最不稳定,不同并发情况下表现不一样:短时间低并发下,效率比synchronized高,有时甚至比LongAdder还高出一点,但是高并发下,性能还不如synchronized,不同情况下性能表现很不稳定: 3. LongAdder性能稳定,在各种并发情况下表现都不错,整体表现最好,短时间的低并发下比AtomicInteger

一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等

重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,到处都是锁. 今天这篇文章,给大家普及下这些锁究竟是啥,他们的由来,他们之间有啥关系,有啥区别. 重量级锁 如果你学过多线程,那么你肯定知道锁这个东西,至于为什么需要锁,我就不给你普及了,就当做你是已经懂的了. 我们知道,我们要进入一个同步.线程安全的方法时,是需要先获得这个方法的锁的,退出这个方法时,则会释放锁.如果获取不到这个锁的话,意味着有别的线程在执行这个方法,这时我们就会马上进入阻塞的状态,等待那

偏向锁,轻量级锁与重量级锁的区别以及如何膨胀

偏向锁 Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁.当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID,以后该线程在进入和退出同步块时不需要花费 CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁, 如果测试失败,则需要再测试下 Mark Word中偏向锁的标识是否设置成 1(

Synchronized的原理及自旋锁,偏向锁,轻量级锁,重量级锁的区别(摘抄和理解)

1. 自旋锁的优缺点: 自旋锁 自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗. 但是线程自旋是需要消耗CPU的,说白了就是让CPU在做无用功,线程不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间. 如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最

偏向锁,轻量级锁,重量级锁

很难懂的概念,我看了两天才明白个大概,先记录一下参考资料,晚一点再写自己的总结 参考资料 java锁优化 JVM内部细节之一:synchronized关键字及实现细节(轻量级锁Lightweight Locking) 聊聊并发(二)Java SE1.6中的Synchronized 虚拟机中的锁优化简介(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)