首页 > 其他分享 >jUC中的锁

jUC中的锁

时间:2024-08-11 23:37:55浏览次数:13  
标签:jUC 对象 线程 偏向 自旋 重量级 轻量级

在JUC中 可以使用synchronized关键字进行加锁
如下所示

Object object = new Object();
synchronized (object){
//            TODO
}

synchronized关键字所加的锁是逐步升级的,顺序是

无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁、

随着锁等级的提高,所带来的消耗也会越大。

在介绍锁直接之前,我们需要先引入一些概念。
在java中,对象由对象头+实例数据+填充数据(可选)组成。而对象头=Marklass+KClass+Body。其中Klass被用来存储对象指向类元数据的指针,虚拟机通过这个指针确定该对象是哪个类的实例。MarkWord则用来存储一些对象自身运行时数据,根据机器不同有32位和64位之分。以64位为例,它在不同锁状态下的结构如下所示
JUC-Monitor-MarkWord结构64位

下面将逐个介绍这些锁

偏向锁

首先是偏向锁,偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程之后重新获取该锁不再需要同步操作。

偏向锁是默认开启的,并且是有延迟的,不会在程序启动时默认生效。原因是在刚开始执行代码时,会有好多线程来抢锁,如果开偏向锁效率反而降低。

  • 使用jvm参数 -XX:BiasedLockingStartupDelay=0 可以禁用掉延迟
  • 使用jvm参数 -XX:-UseBiasedLocking 可以禁用掉偏向锁

当锁对象第一次被线程获得进入偏向状态时,会使用CAS操作将线程ID(53位)记录到Markword中 而且Markwod中的biased_lock=1 锁标志位会变成01。也就是MarkWord的后三位会由正常状态下的001变成101。如果 CAS 操作成功,这个线程以后进入这个锁相关的同步块,查看这个线程 ID 是自己的就表示没有竞争,就不需要再进行任何同步操作。当有其他线程来竞争锁对象时,偏向锁就会被撤销。

偏向锁的撤销

  • 当锁对象的hashcode被调用后,偏向锁机会被撤销,原因是在markword中记录线程ID的字段占用了hashcode的字段(在未偏向时使用hashcode会导致直接成为轻量级锁,在已偏向时调用hashcode会直接升级为重量级锁)。
  • 当有其他线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
  • 调用 wait/notify时,notify会升级为轻量级锁,notify则会升级为重量级锁。
  • 批量撤销 如果对象被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
  • 批量重偏向 当撤销偏向锁阈值超过 20 次后,JVM 会觉得是不是偏向错了,于是在给这些对象加锁时重新偏向至加锁线程
  • 批量撤销 当撤销偏向锁阈值超过 40 次后,JVM 会觉得自己确实偏向错了,根本就不该偏向,于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

轻量级锁

当一个对象有多个对象要加锁,但是时间是错开的,JVM会使用轻量级锁来优化
轻量级锁在没有竞争时(锁重入时),每次重入仍然需要执行 CAS 操作,Java 6 才引入的偏向锁来优化。

加锁过程

  • 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,存储锁定对象的 Mark Word

  • 让锁记录中 Object reference 指向锁住的对象,并尝试用 CAS 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录
    img

  • 如果 CAS 替换成功,对象头中存储了锁记录地址和状态 00(轻量级锁) ,表示由该线程给对象加锁
    img

  • 如果CAS 失败 则有两种情况

    • 一是锁重入 添加一条 Lock Record 作为重入的计数
      img
    • 二是其他线程已经持有该对象的轻量级锁 这时表明存在竞争 会将轻量级锁升级为重量级锁。

解锁过程

  • 如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减 1
  • 如果锁记录的值不为 null,这时使用 CAS 将 Mark Word 的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

重量级锁

当发生多个线程同时竞争时,轻量级锁就会升级为重量级锁,重量级锁的实现依赖于Monitor

Montior

Monitor 被翻译为监视器或管程。每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针。
Montior由Owner EntryList WaitSet三部分组成
img
img

轻量级锁升级(锁膨胀)

在尝试加轻量级锁的过程中,CAS 操作无法成功,可能是其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

  • Thread-1 加轻量级锁失败,进入锁膨胀流程:为 Object 对象申请 Monitor 锁,通过 Object 对象头获取到持锁线程,将 Monitor 的 Owner 置为 Thread-0,将 Object 的对象头指向重量级锁地址,然后自己进入 Monitor 的 EntryList BLOCKED

锁自旋

重量级锁竞争时,尝试获取锁的线程不会立即阻塞,可以使用自旋(默认 10 次)来进行优化,采用循环的方式去尝试获取锁

注意:

  • 自旋占用 CPU 时间,单核 CPU 自旋就是浪费时间,因为同一时刻只能运行一个线程,多核 CPU 自旋才能发挥优势
  • 自旋失败的线程会进入阻塞状态

优点:不会进入阻塞状态,减少线程上下文切换的消耗

缺点:当自旋的线程越来越多时,会不断的消耗 CPU 资源

自旋锁说明:

  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,比较智能
  • Java 7 之后不能控制是否开启自旋功能,由 JVM 控制

参考

本文内容参考以下内容
https://blog.csdn.net/Xin_101/article/details/117568632

标签:jUC,对象,线程,偏向,自旋,重量级,轻量级
From: https://www.cnblogs.com/baihualiaoluan/p/18354123

相关文章

  • JUC并发编程:基于Condition实现一个阻塞队列
    Condition方法概述await():当前线程进入等待状态,直到被通知(siginal)或中断【和wait方法语义相同】。awaitUninterruptibly():当前线程进入等待状态,直到被通知,对中断不敏感。awaitNanos(longtimeout):当前线程进入等待状态直到被通知(siginal),中断或超时。awaitUnit......
  • 锁的分类和JUC
    锁的分类乐观锁、悲观锁对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字是最典型的悲观锁。乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不......
  • JUC(java.util.concurrent)的常见类
    JUC(java.util.concurrent)的常见类Callable(这是一个interface接口)这个也是创建线程的一种方式Runnable能表示一个任务(run方法)返回:voidCallable也能表示一个任务(call方法)返回:一个具体的值,类型可以通过泛型参数来指定(Object)如果进行多线程操作,只是关心多线......
  • JUC工具类: Exchanger详解
    Exchanger是用于线程协作的工具类,主要用于两个线程之间的数据交换。@立刀旁目录#带着BAT大厂的面试问题去理解Exchanger#Exchanger简介#Exchanger实现机制#Exchanger源码解析#内部类-Participant#内部类-Node#核心属性#构造函数#核心方法-exchang......
  • JUC锁: 锁核心类AQS详解
    AbstractQueuedSynchronizer抽象类是核心,需要重点掌握。它提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。@立刀旁目录#带着BAT大厂的面试问题去理解#AbstractQueuedSynchronizer简介#AQS核心思想#AQS对资源的共享方式#AQS底层使用了模......
  • 线程的6种状态(juc编程)
    1线程状态1.1状态介绍当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:publicclassT......
  • JUC并发编程第十四章——线程安全集合类
    1 并发集合1.1 线程安全集合分类a.遗留的线程安全集合遗留的线程安全集合如Hashtable,Vectorb.使用Collections装饰的线程安全集合使用Collections装饰的线程安全集合,如:Collections.synchronizedCollectionCollections.synchronizedListCollections.synchroni......
  • 【JUC】8-CompletableFutrue的常用方法
    1、获得结果和触发计算获得结果1publicTget()23publicTget(longtimeOut,Timeunitunit)45publicTjoin()67publicgetNow(TvalueIfAbsent)主动触发计算publicbooleancomplete(Tvalue) 2、对计算结果进行处理计算结果存在依赖关系,这两个线程串......
  • Java最全知识脑图 涵盖 juc mysql git mybatis 等 面试必备
    Java初中级知识脑图面试超实用1.Git下载链接导图下载地址:https://mm.edrawsoft.cn/mobile-share/index.html?uuid=31d00742157057-src&share_type=12.JUC下载链接https://mm.edrawsoft.cn/mobile-share/index.html?uuid=6c0be457444921-src&share_type=13.JVM下载链......
  • JUC并发编程第十二章——AQS
    1前置知识公平锁和非公平锁公平锁:锁被释放以后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁非公平锁:锁被释放以后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁......