深入理解Java中的偏向锁、轻量级锁与重量级锁

深入理解Java中的偏向锁、轻量级锁与重量级锁

在Java的多线程编程中,锁(Lock)是确保线程安全和协调线程执行的核心机制。为了优化锁的性能,Java虚拟机(JVM)引入了多种锁优化技术,其中最重要的包括偏向锁(Biased Locking)轻量级锁(Lightweight Locking)重量级锁(Heavyweight Locking)。本文将深入探讨这三种锁的原理、工作机制、优缺点及其在实际开发中的应用场景。

目录

  1. 锁的基础知识
  2. 偏向锁(Biased Locking)
    • 原理与工作机制
    • 优缺点
    • 启用与禁用偏向锁
    • 示例
  3. 轻量级锁(Lightweight Locking)
    • 原理与工作机制
    • 优缺点
    • 示例
  4. 重量级锁(Heavyweight Locking)
    • 原理与工作机制
    • 优缺点
    • 示例
  5. 锁优化的转变过程
  6. 实际应用中的选择与优化
  7. 总结

锁的基础知识

在多线程环境中,当多个线程尝试访问共享资源时,用于控制对资源的访问,确保数据的一致性和完整性。Java提供了多种锁机制,其中最常用的是基于synchronized关键字的内置锁。为了提高锁的性能,JVM采用了多种优化技术,包括偏向锁、轻量级锁和重量级锁。

内置锁的基本行为

  • 加锁与解锁:当一个线程进入一个synchronized块或方法时,它会尝试获取相应对象的监视器锁。如果锁被其他线程持有,当前线程会被阻塞,直到锁被释放。
  • 不可重入性:Java的内置锁是可重入锁,即同一个线程可以多次获取同一把锁而不会导致死锁。

锁的性能问题

锁的获取和释放是有开销的,尤其是在高并发的情况下,频繁的上下文切换和线程阻塞会严重影响性能。因此,JVM通过引入偏向锁、轻量级锁和重量级锁来优化锁的性能,减少不必要的同步开销。


偏向锁(Biased Locking)

原理与工作机制

偏向锁是一种锁优化技术,旨在减少在单线程环境下锁的获取与释放开销。其核心思想是:假设同一把锁大多数情况下只被一个线程频繁地获取和释放,那么可以将这把锁偏向于该线程,当该线程再次请求锁时,无需再进行同步操作。

工作流程
  1. 初始状态:对象处于无锁状态。
  2. 第一次获取锁
    • 当一个线程首次获取锁时,锁会被标记为偏向于该线程。
    • 锁的Mark Word中存储了偏向线程的ID。
  3. 偏向线程再次获取锁
    • 如果偏向线程再次请求锁,JVM无需执行任何同步操作,直接进入锁定状态。
  4. 其他线程竞争锁
    • 如果另一个线程尝试获取已经偏向于某个线程的锁,JVM会撤销偏向锁,将其升级为轻量级锁或重量级锁。
    • 偏向锁的撤销需要CAS操作和锁记录的修改,具备一定的开销。

优缺点

优点

  • 减少同步开销:在大多数情况下,锁只被一个线程持有,偏向锁可以显著减少获取锁的开销。
  • 提升性能:尤其在单线程环境或锁竞争不激烈的场景下,偏向锁能够提升程序性能。

缺点

  • 不适用于高竞争场景:一旦多个线程竞争同一把锁,偏向锁需要撤销并升级为其他锁,可能带来额外的性能开销。
  • 增加内存开销:每个对象的Mark Word中需要存储偏向线程的ID。

启用与禁用偏向锁

在Java 6及以上版本,偏向锁是默认启用的。可以通过JVM参数来控制偏向锁的行为:

  • 禁用偏向锁
    -XX:-UseBiasedLocking
    
  • 延迟启用偏向锁(默认为400ms):
    -XX:BiasedLockingStartupDelay=0
    

示例

以下示例展示了偏向锁的基本行为:

public class BiasedLockingExample {public static void main(String[] args) throws InterruptedException {Object lock = new Object();// 第一次获取锁,偏向于主线程synchronized (lock) {System.out.println("主线程获取锁,偏向锁被启用");}Thread.sleep(5000); // 等待其他线程启动Thread thread = new Thread(() -> {synchronized (lock) {System.out.println("子线程获取锁");}});thread.start();thread.join();}
}

输出

主线程获取锁,偏向锁被启用
子线程获取锁

在这个示例中,主线程首次获取锁时,锁会偏向于主线程。之后,子线程尝试获取锁时,偏向锁会被撤销,并升级为轻量级锁或重量级锁,以允许子线程获取锁。


轻量级锁(Lightweight Locking)

原理与工作机制

轻量级锁是一种优化机制,旨在减少在没有锁竞争时获取和释放锁的开销。其核心思想是:当多个线程不竞争同一把锁时,锁的获取与释放可以通过无阻塞的CAS(Compare-And-Swap)操作完成,而无需进行上下文切换。

工作流程
  1. 尝试获取锁
    • 线程尝试通过CAS操作将对象的Mark Word从无锁状态更新为指向当前线程的锁记录。
  2. 获取成功
    • 如果CAS操作成功,线程持有锁,进入锁定状态。
  3. 获取失败
    • 如果CAS操作失败,表示有其他线程持有锁,线程进入BLOCKED状态,尝试获取重量级锁。
  4. 释放锁
    • 线程释放锁时,通过CAS操作将Mark Word恢复为无锁状态。

优缺点

优点

  • 减少同步开销:在无锁竞争的情况下,通过CAS操作快速获取锁,避免了重量级锁的上下文切换开销。
  • 适用于低竞争场景:在多个线程不频繁竞争同一把锁时,轻量级锁能显著提升性能。

缺点

  • 有限的竞争处理能力:一旦多个线程频繁竞争锁,轻量级锁可能无法有效处理,锁会升级为重量级锁。
  • 依赖CAS操作:CAS操作可能导致ABA问题(虽然在JVM中已通过其他机制处理),并且在高并发情况下可能会导致性能下降。

示例

以下示例展示了轻量级锁的基本行为:

public class LightweightLockingExample {public static void main(String[] args) throws InterruptedException {Object lock = new Object();// 第一个线程获取锁,轻量级锁被启用Thread thread1 = new Thread(() -> {synchronized (lock) {System.out.println("线程1获取锁");try {Thread.sleep(1000); // 保持锁一段时间} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1释放锁");}});// 第二个线程尝试获取锁Thread thread2 = new Thread(() -> {synchronized (lock) {System.out.println("线程2获取锁");}});thread1.start();Thread.sleep(100); // 确保线程1先获取锁thread2.start();thread1.join();thread2.join();}
}

输出

线程1获取锁
线程1释放锁
线程2获取锁

在这个示例中,线程1首先获取锁,使用轻量级锁进行同步。线程2在等待线程1释放锁时,会尝试获取锁。由于锁在释放后不再有竞争,线程2能够顺利获取锁,继续执行。


重量级锁(Heavyweight Locking)

原理与工作机制

重量级锁是最基本的锁机制,通常在高并发或锁竞争激烈的情况下使用。当偏向锁和轻量级锁无法满足需求时,锁会升级为重量级锁。重量级锁依赖于操作系统的互斥量(Mutex)来管理锁的获取与释放。

工作流程
  1. 获取锁
    • 线程尝试通过CAS操作获取锁,如果失败,锁会升级为重量级锁。
    • JVM会将线程加入到锁的等待队列中,操作系统负责调度线程。
  2. 阻塞与唤醒
    • 无法获取锁的线程会被阻塞,进入BLOCKED状态。
    • 当持有锁的线程释放锁时,操作系统会唤醒等待队列中的线程之一。
  3. 释放锁
    • 线程释放锁后,锁从重量级锁状态恢复为无锁状态。

优缺点

优点

  • 处理高竞争:能够有效处理多个线程频繁竞争同一把锁的情况。
  • 确保锁的独占性:通过操作系统的互斥量机制,确保锁的严格独占。

缺点

  • 高开销:涉及到操作系统的线程调度和上下文切换,开销较大。
  • 性能瓶颈:在高并发场景下,频繁的锁获取与释放会严重影响程序性能。

示例

以下示例展示了重量级锁的基本行为:

public class HeavyweightLockingExample {public static void main(String[] args) throws InterruptedException {Object lock = new Object();// 创建多个线程竞争同一把锁for (int i = 1; i <= 5; i++) {final int threadNum = i;Thread thread = new Thread(() -> {synchronized (lock) {System.out.println("线程" + threadNum + "获取锁");try {Thread.sleep(1000); // 保持锁一段时间} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程" + threadNum + "释放锁");}});thread.start();}}
}

输出(顺序可能略有不同):

线程1获取锁
线程1释放锁
线程2获取锁
线程2释放锁
线程3获取锁
线程3释放锁
线程4获取锁
线程4释放锁
线程5获取锁
线程5释放锁

在这个示例中,多个线程同时尝试获取同一把锁。由于锁竞争激烈,锁会升级为重量级锁,导致线程被阻塞和唤醒的开销显著增加。


锁优化的转变过程

JVM在运行过程中,根据锁的竞争情况动态调整锁的类型,以实现最佳的性能优化。这一过程如下:

  1. 无锁状态(Unlocked)

    • 对象的Mark Word表示无锁状态。
    • 线程尝试获取锁时,如果成功,锁被偏向于当前线程,进入偏向锁状态。
  2. 偏向锁状态(Biased Locking)

    • 对象的Mark Word存储偏向线程的ID。
    • 偏向线程再次获取锁时,无需进行同步操作,直接进入锁定状态。
    • 如果其他线程尝试获取锁,偏向锁会被撤销,进入轻量级锁状态。
  3. 轻量级锁状态(Lightweight Locking)

    • 通过CAS操作尝试获取锁。
    • 如果成功,锁记录保存在线程的栈中,Mark Word指向锁记录。
    • 如果有竞争,锁会升级为重量级锁。
  4. 重量级锁状态(Heavyweight Locking)

    • 使用操作系统的互斥量管理锁。
    • 线程被阻塞,直到锁被释放。
    • 解锁后,锁恢复为无锁状态或重新进行偏向锁优化。

锁的升级路径

无锁状态 → 偏向锁 → 轻量级锁 → 重量级锁

示例

以下示例展示了锁从偏向锁升级到重量级锁的过程:

public class LockUpgradeExample {public static void main(String[] args) throws InterruptedException {Object lock = new Object();// 首先一个线程获取锁,偏向锁被启用Thread thread1 = new Thread(() -> {synchronized (lock) {System.out.println("线程1获取锁");try {Thread.sleep(2000); // 保持锁一段时间} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1释放锁");}});// 另一个线程尝试获取同一把锁,导致锁升级Thread thread2 = new Thread(() -> {synchronized (lock) {System.out.println("线程2获取锁");}});thread1.start();Thread.sleep(500); // 确保线程1先获取锁thread2.start();thread1.join();thread2.join();}
}

输出

线程1获取锁
线程1释放锁
线程2获取锁

在这个示例中,线程1首先获取锁,偏向锁被启用。线程2尝试获取同一把锁时,偏向锁被撤销,锁升级为重量级锁,使得线程2能够获取锁。


实际应用中的选择与优化

理解偏向锁、轻量级锁与重量级锁的工作原理,有助于在实际开发中做出合适的同步策略选择和性能优化。

1. 减少锁的持有时间

尽量缩小synchronized块的范围,减少锁的持有时间,降低锁竞争的概率。

// 不推荐
synchronized(lock) {// 大量处理逻辑
}// 推荐
synchronized(lock) {// 只处理必要的同步操作
}
// 大量处理逻辑

2. 使用更细粒度的锁

将一个大的锁拆分为多个小的锁,降低锁的竞争程度。

// 不推荐:一个锁保护多个资源
synchronized(lock) {// 资源A的操作// 资源B的操作
}// 推荐:分别使用不同的锁保护不同的资源
synchronized(lockA) {// 资源A的操作
}
synchronized(lockB) {// 资源B的操作
}

3. 利用无锁数据结构

在可能的情况下,使用Java并发包(java.util.concurrent)提供的无锁或高效锁的数据结构,如ConcurrentHashMapCopyOnWriteArrayList等,避免显式的synchronized同步。

// 使用 ConcurrentHashMap 代替 HashMap + synchronized
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");

4. 合理配置JVM参数

根据应用的特性和需求,调整JVM的锁优化参数,如偏向锁的启用与禁用、偏向锁的延迟启动时间等。

# 禁用偏向锁
-XX:-UseBiasedLocking# 设置偏向锁启动延迟为0,立即启用
-XX:BiasedLockingStartupDelay=0

5. 避免过度同步

不要对不必要的代码块进行同步,避免引入不必要的锁竞争和性能开销。

// 不推荐:过度同步
public void update() {synchronized(lock) {// 不需要同步的操作this.value = newValue;}
}// 推荐:只同步需要保护的部分
public void update() {this.value = newValue; // 不需要同步
}

总结

Java中的偏向锁、轻量级锁与重量级锁构成了一套复杂而高效的锁优化机制,旨在在不同的并发场景下提供最佳的性能表现:

  • 偏向锁:适用于锁主要被一个线程持有的场景,减少了锁的获取与释放开销。
  • 轻量级锁:适用于锁被少量线程竞争的场景,通过CAS操作实现快速的锁获取。
  • 重量级锁:适用于锁被高频率、多线程竞争的场景,确保锁的独占性。

通过合理理解和应用这些锁机制,结合良好的并发编程实践,可以显著提升Java应用程序的性能和响应性。同时,随着Java版本的不断演进,锁的优化机制也在持续改进,开发者应及时关注最新的JVM优化技术,以充分利用其带来的性能优势。

希望本文能够帮助你深入理解Java中的偏向锁、轻量级锁与重量级锁,并在实际开发中灵活应用。如有任何问题或需要进一步探讨,欢迎在评论区留言交流!

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

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

相关文章

带你0到1之QT编程:十一、掌握Containers容器艺术,一网打尽开发利器

此为QT编程的第十一谈&#xff01;关注我&#xff0c;带你快速学习QT编程的学习路线&#xff01; 每一篇的技术点都是很很重要&#xff01;很重要&#xff01;很重要&#xff01;但不冗余&#xff01; 我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点&#xff01; …

C#中判断socket是否已断开的方法

代码如下&#xff1a; Socket s new Socket(..); if (s.Poll(-1, SelectMode.SelectRead)) {int nRead s.Receive();if (nRead 0){//socket连接已断开} }参考&#xff1a;C#中判断socket是否已断开的方法

【CSS in Depth 2 精译_031】5.3 Grid 网格布局的两种替代语法

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

Corrupt block relative dba: 0x02c0b382 (file 11, block 45954)

接前面断电故障处理2&#xff1a;oracle数据库断电无法启动恢复-CSDN博客 DM00 started with pid145, OS id16516, job SYS.SYS_IMPORT_TABLE_01 2024-09-13T20:05:22.33130208:00 ADVISORY: Please collect redo for investigation of ORA-8103. Use command: ALTER SYSTE…

计算机毕业设计 扶贫助农系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

浅谈人工智能之基于ollama本地大模型结合本地知识库搭建智能客服

浅谈人工智能之基于ollama本地大模型结合本地知识库搭建智能客服 摘要 随着人工智能技术的飞速发展,基于大型语言模型(LLMs)的智能客服系统逐渐成为提升企业服务质量和效率的关键工具。然而,对于注重数据隐私和安全的企业而言,使用云服务可能会引发数据泄露的风险。因此…

基于微信小程序的图书馆预约占座系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的图…

(k8s)kubernetes 挂载 minio csi 的方式(pod挂载pvc存在csi驱动问题,挂载不上)

一、安装Minio&#xff08;Minio分布式集群搭建部署_minio集群最少几台-CSDN博客&#xff09; 生成accessKeyID和secretAccessKey&#xff1a; 二、安装csi-s3插件(在k8s集群上) 首先我们把插件的yaml文件都下载下来&#xff0c;为了保证版本测试的一致性&#xff0c;我们下载…

前端面试常考算法

快速排序 #include<iostream> #include<cstdio> using namespace std; const int N 100005; int a[N];void quick_sort(int a[], int l, int r) {if (l > r) return;int x a[l r >> 1];int i l - 1, j r 1;while (i < j) {while (a[i] < x);…

使用 C++ 过滤文件中的纯英文字符串

在处理多语言文本数据时&#xff0c;经常会遇到需要过滤掉非目标语言字符串的情况。例如&#xff0c;你可能有多个文件&#xff0c;每个文件中存放了不同语言的字符串&#xff0c;但这些文件中都混杂了一些纯英文的字符串。本文将介绍如何使用 C 实现只获取每个文件中的纯英文字…

什么是 PHP? 为什么用 PHP? 谁在用 PHP?

一、什么是 PHP&#xff1f; PHP&#xff08;Hypertext Preprocessor&#xff0c;超文本预处理器&#xff09;是一种广泛应用于 Web 开发的通用开源脚本语言。 PHP 主要用于服务器端编程&#xff0c;可以嵌入 HTML 中&#xff0c;与数据库进行交互&#xff0c;生成动态网页内容…

FreeRTOS—任务通知

一&#xff0c;概念介绍 队列、信号量、事件组等IPC技术都需要创建一个中间对象进程之间通过这些中间对象进行通讯或同步。创建对象就需要分配内存&#xff0c;占用一定内存。 二&#xff0c;任务通知的特点&#xff1a; 一个任务或ISR向另外一个指定的任务发送通知&#xff0c…

微信小程序中的模块化、组件化开发:完整指南

文章目录 前言一、模块化与组件化开发的优势1.1模块化开发的优势1.2 组件化开发的优势 二、组件的抽离标准及规范2.1 抽离组件的标准2.2 组件化开发规范 三、模块化规范的种类及优劣比较3.1 CommonJS3.2 ES6 Modules3.3 优劣对比 四、组件封装&#xff1a;全局组件、分包组件、…

浅谈树型结构——树

文章目录 一、什么是树&#xff1f;二、树的特点三、树的概念四、树的表示形式五、树的应用 一、什么是树&#xff1f; 树是一种 非线性 的数据结构&#xff0c;是树型结构。是一个由n个有限结点组成的一个具有层次关系的集合&#xff0c;这种集合因为看起来像一颗倒挂的树&am…

波克城市 x NebulaGraph|高效数据血缘系统在游戏领域的构建实战

关于波克城市和作者‍‍ 波克城市&#xff0c;一家专注于研发精品休闲游戏的全球化公司&#xff0c;连续七年入选中国互联网综合实力百强&#xff0c;2023 年位列 17 位。波克城市旗下拥有《捕鱼达人》《猫咪公寓2》等精品休闲游戏&#xff0c;全球注册用户超 5 亿&#xff0c;…

系统架构师---介绍ER图

E-R图&#xff0c;全称为Entity-Relationship Diagram&#xff0c;即实体联系图或实体关系图&#xff0c;是一种用于描述数据库中实体及其关系的数据建模工具。它是数据库设计中的重要概念模型&#xff0c;通过图形化的方式展示了实体、属性和它们之间的关系&#xff0c;帮助开…

借老系统重构我准备写个迷你版apiFox

前段时间一直在忙公司老系统重构的方案设计&#xff0c;其中最大的重构点就是前后端分离。为了加快前后端协同开发和对接的工作效率&#xff0c;我决定写一个公司内部使用的迷你版的apiFox。 文章目录 有现成的工具为啥不用现有成熟方案初步成果展示下一步计划 有现成的工具为啥…

Kafka+PostgreSql,构建一个总线服务

之前开发的系统&#xff0c;用到了RabbitMQ和SQL Server作为总线服务的传输层和存储层&#xff0c;最近一直在看Kafka和PostgreSql相关的知识&#xff0c;想着是不是可以把服务总线的技术栈切换到这个上面。今天花了点时间试了试&#xff0c;过程还是比较顺利的&#xff0c;后续…

华为CNA VRM搭建(使用vmware worfstartion搭建)

创建虚拟机&#xff1a; 自定义→高级 选择硬件兼容性&#xff1a;默认安装版本&#xff0c;如果未来想要将此虚拟机安装到其他电脑&#xff0c;其他电脑版本过低&#xff0c;此时可以向下兼容&#xff0c;这里我们默认版本 稍后安装操作系统&#xff1a; CNA采用Euler OS系统…

MySQL练手题--体育馆的人流量(困难)

一、准备工作 Create table If Not Exists Stadium (id int, visit_date DATE NULL, people int); Truncate table Stadium; insert into Stadium (id, visit_date, people) values (1, 2017-01-01, 10); insert into Stadium (id, visit_date, people) values (2, 2017-01-02…