【JUC】二十八、synchronized锁升级之偏向锁

news2024/12/22 20:56:54

文章目录

  • 1、偏向锁出现的背景
  • 2、从共享对象的内存结构看偏向锁
  • 3、偏向锁的持有
  • 4、启动偏向锁
  • 5、sleep暂停来启动偏向锁
  • 6、偏向锁的撤销
  • 7、总体流程
  • 8、SinceJava15 偏向锁的废除

在这里插入图片描述

1、偏向锁出现的背景

如果一个线程连续几次抢到锁,仍然重复加锁解锁,就会导致用户态和内核态频繁切换,这显然是有改进空间的。如之前买票的例子:

public class SaleTick {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket.sale();
            }
        },"t1").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket.sale();
            }
        },"t2").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket.sale();
            }
        },"t3").start();
    }
}


//资源类
class Ticket {

    private int number = 50;
    Object lockObject = new Object();

    public void sale() {

        synchronized (lockObject) {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出票,剩余票数" + number--);
            }
        }
    }
}

发现一个线程一直在抢到锁:

在这里插入图片描述

Hotspot 的作者发现,大多数情况下:多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步代码块时提高性能。

偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也即偏向锁在资源没有竞争情况下消除了同步语句,懒的连CAS操作都不做了,直接提高程序性能。

举个例子:生活中,第一次去店铺A吃牛肉汤,老板会问你的口味,然后接下来几天,天天都去吃这家店,那老板以后看到来的是你,就不会再问了,直接给你按口味做就是了。

2、从共享对象的内存结构看偏向锁

从对象结构来看,偏向锁时,被锁对象请求头Mark word的前54位都存当前线程的指针,末尾的三位则改成了101,即代表偏向锁。

在这里插入图片描述

3、偏向锁的持有

当线程A第一次竞争到对象锁时,修改共享对象Mark Word里的偏向线程ID,在没有其他线程竞争的情况下,后续这个线程再进入这个同步代码块时,不需要再次加锁解锁,只需判断对象Mark Word里的ID是不是指向自己

  • 是,就直接执行,且直到有其他线程过来发生竞争才释放锁
  • 不是,说明发生了竞争,就尝试通过CAS修改Mark Word里的线程ID为自身ID

上面CAS时:

  • 如果修改成功,说明线程B来改时,之前偏向的线程A刚结束,此时,仍为偏向锁,偏向B
  • 如果修改失败,升级轻量锁,保证所有线程重新公平竞争

注意点:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的

在这里插入图片描述

偏向锁的操作不会直接捅到操作系统,不涉及用户到内核来回转换。以自定义的Account对象的对象头为例:

在这里插入图片描述

此时,线程A执行到synchronized同步代码块时,JVM通过CAS操作把线程指针ID记录到Account对象的Mark word 中,并修改偏向标识,线程A获得锁成功。注意,执行完同步代码块后,锁并未释放,等线程A二次来时,JVM判断account的Mark Word里面是否还有线程A的ID,有,就继续执行,因为之前没有释放锁,这里自然不用重新获取锁,也就不涉及用户态和内核态的来回切换

在这里插入图片描述

4、启动偏向锁

终端执行以下,查看偏向锁的配置信息:

java -XX:+PrintFlagsInitial | grep BiasedLock*

可以看到偏向锁默认打开,以及启动偏向锁的延迟时长(默认延迟4秒,我这里JDK版本较高,不是4)

在这里插入图片描述

写实例Demo:

在这里插入图片描述

可以看到只有一个线程在操作对象o ⇒ 应该是偏向锁 ⇒ 却发现是轻量锁000

在这里插入图片描述

这是因为偏向锁延时4秒开启,期间自然是下一级:轻量锁。

偏向锁在JDK1.6之后就默认开启,但启动时间有延迟,想立刻启动,可通过添加JVM参数将延迟改为0:

  • -XX:+UseBiasedLocking 开启偏向锁
  • -XX:-UseBiasedLocking 关闭偏向锁,此时会直接跳入轻量锁
  • -XX:BiasedLockingStartupDelay=0 关闭延迟

添加JVM参数,这里关闭延时,正常显示101,即偏向锁:

在这里插入图片描述

5、sleep暂停来启动偏向锁

除了以上添加JVM参数关闭延时来立刻启动偏向锁,也可通过另一种方式:程序执行前等4秒,以保证开启了偏向锁

在这里插入图片描述

再对比下,偏向锁开启后,使用synchronized锁时的对象o和不使用synchronized时的对象o的区别:

在这里插入图片描述

可以看到二者锁状态均为101,但前面o对象未使用synchronized锁,所以线程ID为空,而后者则带了线程ID。

6、偏向锁的撤销

共享对象o的Mark Word一直指向线程A的ID,线程A也一直拿着这个对象锁。直到第二个线程开始来抢夺锁时,线程A的好日子结束:

  • 偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。

  • 且撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行(客人还在你店里吃饭,你总不能一到打烊时机就掀桌子)

    在这里插入图片描述

  • 如果第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级(升级为轻量锁),且此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁

  • 第一个线程执行刚好完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向。直白说就是,另一线程t2来竞争时,偏向的线程t1刚好执行完,那大家就重新竞争。当然,也有可能t1出代码块后,run方法结束,直接走了,那就偏向t2就行

7、总体流程

在这里插入图片描述

8、SinceJava15 偏向锁的废除

JDK15:Disable and Deprecate Biased Locking.
//2020.9.15

Prior to JDK 15, biased locking is always enabled and available. With this JEP, biased locking will no longer be enabled when HotSpot is started unless -XX:+UseBiasedLocking is set on the command line.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1309099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用 Redis 快速实现分布式锁?

本文我们来讨论如何使用 Redis 快速实现分布式锁。 分布式锁有很多种解决方案&#xff0c;前面简单介绍过&#xff0c;Redis 可以通过 set key 方式来实现分布式锁&#xff0c;但实际情况要更加复杂&#xff0c;比如如何确保临界资源的串行执行&#xff0c;如何及时释放&#…

HarmonyOS、ArkTS 备忘录(持续更新中)

Component 、Builder Component封装大的组件Builder自定义构建函数&#xff0c;可以理解为 构建页面的函数&#xff1b;Builder插槽多点&#xff0c;封装一些小的模块 组件状态管理 像素单位 export default 和 export之间的区别

鸿蒙ArkTS Web组件加载空白的问题原因及解决方案

问题症状 初学鸿蒙开发&#xff0c;按照官方文档Web组件文档《使用Web组件加载页面》示例中的代码照抄运行后显示空白&#xff0c;纠结之余多方搜索后扔无解决方法。 运行代码 import web_webview from ohos.web.webviewEntry Component struct Index {controller: web_webv…

企业计算机服务器中了halo勒索病毒怎么解密,勒索病毒解密数据恢复

在网络技术飞速发展的今天&#xff0c;越来越多的企业开始意识到企业数据安全的重要性&#xff0c;很多企业都会依赖数字化办公系统软件&#xff0c;并且通过系统软件将企业的重要数据存储在数据库中&#xff0c;为企业的生产运营提供了极大便利&#xff0c;但网络威胁一直存在…

关于在Java中打印三角形图形的汇总

前面写过一些关于打印三角形图形代码的文章&#xff0c;这里进行了汇总&#xff0c;话不多说&#xff0c;直接上代码&#xff1a; /*** 关于打印三角形的汇总*/ public class Work1 {public static void main(String[] args) {int num 5;/** 打印如下图形&#xff1a;* ** …

基于单片机智能循迹小车仿真设计

**单片机设计介绍&#xff0c;基于单片机智能循迹小车仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能循迹小车是一种通过传感器检测地面情况&#xff0c;并根据设定的规则进行动作控制的机器人。它使用…

如何正确使用缓存来提升系统性能

文章目录 引言什么时候适合加缓存&#xff1f;示例1示例2&#xff1a;示例3&#xff1a; 缓存应该怎么配置&#xff1f;数据分布**缓存容量大小&#xff1a;**数据淘汰策略 缓存的副作用总结 引言 在上一篇文章IO密集型服务提升性能的三种方法中&#xff0c;我们提到了三种优化…

LeetCode Hot100 39.组合总数

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…

css3实现动态心电图折线

css3实现动态心电图折线 M&#xff08;moveto&#xff09;&#xff1a;需要两个参数&#xff08;x轴和y轴坐标&#xff0c;移动到的点的x轴和y轴的坐标L&#xff08;lineto&#xff09;&#xff1a;需要两个参数&#xff08;x轴和y轴坐标&#xff09;&#xff0c;它会在当前位置…

04.HTML其他知识

HTML其他知识 1.HTML实体 介绍 在 HTML 中我们可以用一种特殊的形式的内容&#xff0c;来表示某个符号&#xff0c;这种特殊形式的内容&#xff0c;就是 HTML 实体。比如小于号 < 用于定义 HTML 标签的开始。如果我们希望浏览器正确地显示这些字符&#xff0c;我们必须在…

CGAL的3D网格生成

1、介绍 该软件包致力于生成离散三维域的各向同性简化网格。要网格化的域是三维空间的子集&#xff0c;需要有界。域可以连接或由多个组件组成和/或细分为几个子域。 边界曲面和细分曲面是平滑曲面或分段平滑曲面&#xff0c;由平面或曲面面片形成。表面可能表现出一维特征&…

成都工业学院2021级操作系统专周课程设计FCFS,SSTF,SCAN,LOOK算法的实现

运行环境 操作系统&#xff1a;Windows 11 家庭版 运行软件&#xff1a;CLion 2023.2.2 源代码文件 #include <iostream> #include <vector> #include <algorithm> #include <random> using namespace std;// 生成随机数 int generateRandomNumber…

12.13_黑马数据结构与算法笔记Java

目录 098 堆 heapify 3 099 堆 增删替换 100 堆 e01 堆排序 100 堆e02 求数组第k大元素 100 堆e03 求数据流第k大元素 100 堆e04 求数据流中位数1 100 堆e04 求数据流中位数2 100 堆e04 求数据流中位数3 101 二叉树 概述 102 二叉树 深度优先遍历 103 二叉树 前中后…

2024年顶级的9个 Android 数据恢复工具(免费和付费)

不同的事情可能会损坏您的Android手机并导致您丢失数据。但大多数时候&#xff0c;您可以使用取证工具恢复部分或全部文件。 问题可能来自手机的物理损坏、磁盘的逻辑故障、完整的系统擦除&#xff0c;或者只是简单的粗心大意。 但是&#xff0c;无论数据丢失的原因是什么&am…

路由基本原理

目录 一、路由器概述 二、路由器的工作原理 三、路由表的形成 四、路由配置 1.连接设备 2.进入系统模式 3.进入接口模式 4.配置网络 5.下一跳的设置 6.设置浮动路由 7.设置默认路由 一、路由器概述 路由器&#xff08;Router&#xff09;是一种用于连接不同网络或子…

MySQL和Redis有什么区别?

目录 一、什么是MySQL 二、什么是Redis 三、MySQL和Redis的区别 一、什么是MySQL MySQL是一种开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是最流行的数据库之一。MySQL以其高性能、可靠性和易用性而闻名&#xff0c;广泛应用于各种Web应用程序…

单片机的低功耗模式介绍

文章目录 简介一、功耗来源说明1.1、芯片工作模式1.2、静态损耗1.3、I/O额外损耗1.4、动态损耗 二、功耗如何测量三、降低功耗有什么方法3.1、选取合适的芯片工作模式3.2、降低工作频率3.3、关闭不需要使用的外设3.4、 降低静态电流损耗3.5、 周期采集供电3.6、 设置IO口状态 四…

MYSQL各种日志

感谢B站up主的视频分享 黑马程序员 MySQL数据库入门到精通&#xff0c;从mysql安装到mysql高级、mysql优化全囊括_哔哩哔哩_bilibili

FMETP STREAM 2.0

FMETPSTREAM简化了Unity3D中的直播,无需编码。设置和测试仅需5分钟。 "编码器模块"将Unity游戏视图、网络摄像头、桌面、声音和麦克风输入转换为字节数据,使其完美适用于各种流媒体场景。 优化的网络模块支持Server-clients连接类型,并允许您使用单个命令向 Serve…

el-date-picker限制选择7天内禁止内框选择

需求&#xff1a;elementPlus时间段选择框需要满足&#xff1a;①最多选7天时间。②不能手动输入。 <el-date-picker v-model"timeArrange" focus"timeEditable" :editable"false" type"datetimerange" range-separator"至&qu…