聊聊JAVA中的锁优化锁升级及其底层原理剖析

news2024/11/20 15:22:39

文章目录

  • 1. 基础介绍
    • java中的锁
    • 什么是锁优化和锁升级
  • 2. Java中的锁升级过程及底层原理
    • 1. 偏向锁实现机制和原理
      • 1. 偏向锁的原理
      • 2. 偏向锁的底层实现
    • 2. 轻量级锁
      • 1. 轻量级锁的原理
      • 2. 轻量级锁的底层实现
    • 3. 重量级锁
      • 1. 重量级锁的原理
      • 2. 重量级锁的底层实现
  • 3. Java中锁升级的详细过程剖析
    • 1. 锁升级的触发条件
    • 2. 偏向锁、轻量级锁、重量级锁之间的转换过程
    • 3. 锁升级过程的具体实例分析
  • 4. Java中的锁优化
    • 1. 锁优化的方法
    • 2. 锁消除
      • 什么是锁消除
      • 锁消除的原理:
      • 锁消除的实例剖析:
    • 3. 锁粗化
      • 什么是锁粗化
      • 锁粗化的原理:
      • 锁粗化的实例剖析:
  • 5. 参考文档

在这里插入图片描述

1. 基础介绍

java中的锁

在Java中,锁是实现多线程并发控制的一种重要机制。它可以保证多个线程之间安全地访问共享资源,防止数据的不一致性。锁有两种类型:内部锁和外部锁。内部锁是通过synchronized实现的,它可以解决方法或代码块在多线程环境下的同步问题。外部锁则是通过ReentrantLock等类实现的,除了能解决同步问题外,还提供了更多高级功能,如公平锁、非公平锁、条件等待/通知等。

什么是锁优化和锁升级

锁优化是为了提高系统的并发性能,包括减少锁的竞争、减小锁的粒度、减少锁的持有时间等,以减少线程等待锁的时间,提高系统的吞吐量。
锁升级是指,当锁的竞争情况变得激烈时,JVM会将锁的状态由轻量级锁升级为重量级锁,以保证线程安全。同样,当锁的竞争情况减轻时,JVM也会将锁的状态由重量级锁降级为轻量级锁,以提高系统的并发性能。
锁优化和锁升级的重要性在于,它们可以在保证线程安全的前提下,提高系统的并发性能,从而提高系统的吞吐量。在高并发的系统中,锁优化和锁升级是不可或缺的。

2. Java中的锁升级过程及底层原理

1. 偏向锁实现机制和原理

偏向锁是Java 6引入的一种新的锁优化策略。它的主要思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时记录下这个线程的信息,当这个线程再次请求锁时,无需再做任何同步操作。这样做的目的是为了消除无竞争的同步原语,进一步提高程序的运行性能。如果有其他线程尝试获取这个锁,那么偏向模式就会被关闭。偏向锁适用于只有一个线程访问同步块的场景。

1. 偏向锁的原理

偏向锁的核心思想是,在无竞争的情况下,把整个同步消除掉。也就是说,如果一个锁只被一个线程锁定,而没有其他线程来竞争这个锁,那么这个锁就会偏向于这个线程,从而消除这个锁的同步操作。

2. 偏向锁的底层实现

偏向锁的底层实现,主要是通过在对象头中的Mark Word里存储偏向的线程ID来实现的。当线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID。如果下次该线程再次尝试获取这个锁,由于检查到锁对象已经偏向于自己,所以无需再做任何同步操作。只有当其他线程尝试获取这个锁时,应用程序才需要做出真正的同步操作,例如撤销偏向锁,升级为轻量级锁等。

2. 轻量级锁

轻量级锁是用来提高线程并发性的一种锁优化策略,主要针对锁的竞争不激烈的场合。轻量级锁相比于重量级锁(即操作系统层面的锁),其等待是通过自旋实现的,不会将线程状态置为阻塞,从而减少了不必要的线程上下文切换。

1. 轻量级锁的原理

当一个线程尝试获取某个轻量级锁时,它首先会检查这个锁是否处于偏向状态,如果不是,它会在自己的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,然后尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果成功,那么这个线程就持有了这个轻量级锁。如果失败,那么它会检查对象头中的Mark Word是否指向自己的锁记录,如果是,那么它就成功地获取到了轻量级锁;否则,它会自旋等待或者升级为重量级锁。

2. 轻量级锁的底层实现

轻量级锁的底层实现主要依赖于CAS操作和自旋技术。CAS操作是用于实现非阻塞性同步的基础,它可以在无锁的情况下保证共享数据的同步;自旋则是为了避免线程在获取不到锁时,立即进入阻塞状态,而是进行几个轮次的循环尝试,看是否能够获取到锁,从而减少线程上下文切换的开销。

3. 重量级锁

重量级锁是Java中最传统的synchronized锁,是一种互斥锁,它是依赖于JVM和操作系统的线程调度机制。当一个线程获得一个对象的重量级锁后,其他任何线程都无法再获得该对象的锁,如果还有线程尝试获取该对象的锁,就会被阻塞住,直到锁的所有者线程释放该锁。

1. 重量级锁的原理

当一个线程尝试获取一个对象的重量级锁时,如果该对象的锁已经被其他线程持有,那么该线程就会被阻塞,即不会继续执行,进入阻塞状态。直到持有锁的线程释放了锁,JVM才会从被阻塞的线程中选择一个,将锁分配给它,其他的线程仍然保持阻塞状态。

2. 重量级锁的底层实现

重量级锁的底层主要依赖于操作系统的Mutex Lock(互斥锁)来实现,当一个线程获取不到锁时,它会进入阻塞状态,等待锁的释放。在这个过程中,涉及到操作系统用户模式和内核模式的转换,以及线程的调度和切换,这些都是需要消耗大量系统资源的操作,因此称为“重量级锁”。

适用场景:当锁的竞争非常激烈,即锁保护的代码经常会被多个线程同时执行时,重量级锁的开销反而相对较小。因为这种情况下线程如果不进入阻塞状态,而是采用自旋等待锁的释放,不仅会占用CPU资源,而且由于锁的竞争激烈,线程获取锁的成功率很低,因此重量级锁更加适合。

3. Java中锁升级的详细过程剖析

1. 锁升级的触发条件

锁的升级主要有以下几种触发条件:

  1. 当一个线程持有偏向锁,而另一个线程也试图获取这个锁的时候,偏向锁就会升级为轻量级锁。
  2. 当一个线程持有轻量级锁,而另一个线程也试图获取这个锁的时候,轻量级锁就会升级为重量级锁。
  3. 当线程进行了一定次数的自旋尝试,但仍然无法获取轻量级锁,这个时候就会升级为重量级锁。

2. 偏向锁、轻量级锁、重量级锁之间的转换过程

偏向锁升级为轻量级锁:当一个线程获取了偏向锁,而另一个线程也试图获取这个锁的时候,持有偏向锁的线程会被挂起,JVM会撤销偏向锁,然后把锁升级为轻量级锁。

轻量级锁升级为重量级锁:当轻量级锁竞争失败时,如果自旋等待也不能获取到锁,这个时候就会把轻量级锁升级为重量级锁。此时,没有获取到锁的线程会进入阻塞状态,等待锁释放。

3. 锁升级过程的具体实例分析

例如有两个线程A和B,它们要竞争同一个锁。开始时,该锁是偏向锁状态,线程A首先获取到了这个偏向锁,然后线程B也试图获取这个锁,这时偏向锁就会升级为轻量级锁,线程A会被挂起。然后线程B开始自旋等待,试图获取轻量级锁,如果自旋等待成功,那么线程B就获取到了轻量级锁;如果自旋等待失败,那么轻量级锁就会升级为重量级锁,线程B会进入阻塞状态,等待锁释放。

Java代码示例0
这段代码的运行先后顺序能够模拟出锁的升级过程。首先,线程1获取到偏向锁;然后,线程2尝试获取锁,这会导致偏向锁升级为轻量级锁;最后,线程3尝试获取锁,这会导致轻量级锁升级为重量级锁。

偏向锁和轻量级锁的升级过程是在Java HotSpot VM中的实现,不同的虚拟机实现可能会有所不同。代码中的sleep方法只是为了模拟锁的升级过程,真实的锁升级过程是在JVM内部进行的。

public class LockUpgradeDemo {
    private static Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        // 线程1获取偏向锁
        new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // 确保线程1获取偏向锁
        Thread.sleep(100);
        
        // 线程2尝试获取锁会导致偏向锁升级为轻量级锁
        new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // 确保线程2开始运行
        Thread.sleep(100);
        
        // 线程3尝试获取锁会导致轻量级锁升级为重量级锁
        new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

4. Java中的锁优化

1. 锁优化的方法

  1. 减少锁的持有时间:只在必要的时候持有锁,尽量缩短锁的持有时间,从而减少线程阻塞的可能性。

  2. 减少锁的粒度:使用更细粒度的锁,例如,如果只有一个线程会访问一个对象的某个字段,那么就不需要对整个对象加锁。

  3. 锁分离:如果一个类有多个独立的操作,那么可以为每个操作使用不同的锁,这样就可以避免不必要的同步。

  4. 锁粗化:如果一个线程在一段时间内会多次获取和释放同一个锁,那么JVM可能会尝试将这些操作合并为一次,这就是锁粗化。

  5. 锁消除:如果JVM检测到一段代码中的锁操作是不必要的,那么可能会消除这个锁操作。

  6. 使用无锁数据结构:例如,Java的java.util.concurrent包中提供了许多无锁数据结构,如ConcurrentHashMap、CopyOnWriteArrayList等。

  7. 使用读写锁:如果一个数据结构的读操作比写操作更频繁,那么使用读写锁可以提高性能。

  8. 使用偏向锁和轻量级锁:JVM在1.6之后引入了偏向锁和轻量级锁,用于优化无竞争的同步代码,可以有效减少无必要的重量级锁操作。

我们本章节主要了解核心的两种锁消除和锁粗化

2. 锁消除

什么是锁消除

  锁消除是Java中的一种锁优化技术。优化后的代码可以避免因为竞争锁而导致的线程阻塞,从而提高系统的运行性能。

锁消除的原理:

锁消除的主要原理是在编译阶段,通过一种叫做逃逸分析的技术,分析对象的作用域,发现一些在多线程环境下不可能存在共享资源竞争的情况,从而消除不必要的同步措施。

举个例子,如果某个对象只在一个线程的作用域内使用,那么它就不可能被其他线程访问到,因此,这个对象上的synchronized关键字就没有任何实际的意义,可以被安全地删除。

锁消除的实例剖析:

public class LockElimination {
    public void append(String str1, String str2){
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
        System.out.println(sb.toString());
    }
}

在这个例子中,StringBuffer是线程安全的,内部的append方法是加了synchronized关键字的。但是在append方法中,sb这个对象只会在当前线程中使用,其他线程无法访问到这个对象,因此sb对象上的synchronized关键字实际上是没有必要的。在运行时,JVM会自动消除这个锁,提高程序的性能。

3. 锁粗化

什么是锁粗化

锁粗化是Java中的另一种锁优化策略,主要用于减少线程获取和释放锁的次数。

锁粗化的原理:

锁粗化的基本思想是将多个连续的加锁、解锁操作合并为一次,将加锁的同步范围扩大到其外部。这样,可以减少同步的次数,提高性能。当然,锁粗化的前提是,必须保证扩大后的同步代码块的执行不会影响到程序的并行度。

锁粗化的实例剖析:

我们举个栗子

public class LockCoarsening {
    private StringBuffer sb = new StringBuffer();

    public void append(String str){
        for (int i = 0; i < 10000; i++) {
            sb.append(str);
        }
    }
}

在这个例子中,每次调用append方法时,都会连续进行10000次加锁和解锁操作。这种情况下,JVM会自动进行锁粗化,将这10000次加锁操作合并为一次,将锁的范围扩大到整个append方法。这样做可以显著减少锁的竞争,提高程序的性能。

5. 参考文档

  1. 《深入理解Java虚拟机:JVM高级特性与最佳实践》 - 周志明

  2. https://www.baeldung.com/java-synchronized

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

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

相关文章

selenium +IntelliJ+firefox/chrome 环境全套搭配

1第一步&#xff1a;下载IntelliJ idea 代码编辑器 2第二步&#xff1a;下载浏览器Chrome 3第三步&#xff1a;下载JDK 4第四步&#xff1a;配置环境变量&#xff08;1JAVA_HOME 2 path&#xff09; 5第五步&#xff1a;下载Maven 6第六步&#xff1a;配置环境变量&#x…

新环境下配置jupyter notebook并启动

1.先切换到我们需要使用的环境 2.安装python内核 conda install ipykernel 3.创建指定内核 ipython kernel install --user --nametensorflow2 4.运行jupyter notebook 即出现新创建的内核

计算机网络 第一章计算机网络体系结构

非常好的课程。计算机网络 //----------------------------------------------------------------------------------------------------------------// 需要清楚实体、协议、服务。 网络协议三要素&#xff0c;语法、语义、同步。

遗留系统陷入困境

当我们谈论遗留系统时&#xff0c;我们经常会想到数据中心某处的过时服务器和交换机。我们带着一种病态的迷恋阅读了有关系统性技术问题的文章&#xff0c;这些问题在假期周末困扰着其他旅行者&#xff0c;并为他们缺乏远见而摇头。 然后&#xff0c;我们坐在屏幕前&#xff0…

数据结构—栈、队列、链表

一、栈 Stack&#xff08;存取O(1)&#xff09; 先进后出&#xff0c;进去123&#xff0c;出来321。 基于数组&#xff1a;最后一位为栈尾&#xff0c;用于取操作。 基于链表&#xff1a;第一位为栈尾&#xff0c;用于取操作。 1.1、数组栈 /*** 基于数组实现的顺序栈&#…

4 Ways to Fix an Operation Did Not Complete Virus Error on Windows

文章目录 Can’t open a file on your PC? Use these methods to fix your issue. A Windows operation did not complete virus error.Mahesh Makvana / How-To Geek Readers like you help support How-To Geek. When you make a purchase using links on our site, we ma…

Spring实例化源码解析之registerBeanPostProcessors(六)

BeanPostProcessors是Spring框架中的一个扩展机制&#xff0c;它允许开发人员在Spring容器实例化、配置和初始化Bean的过程中干预和定制化。BeanPostProcessor接口定义了两个方法&#xff1a;postProcessBeforeInitialization和postProcessAfterInitialization&#xff0c;分别…

【WSN】无线传感器网络 X-Y 坐标到图形视图和位字符串前缀嵌入方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MacOS - Sonoma更新了啥

1 系统介绍 苹果公司于2023年9月26日发布了macOS Sonoma 14.0正式版。名称由来不知道&#xff0c;可能是地名&#xff1a;Sonoma是一个地名,指加利福尼亚州北部索诺玛县(Sonoma County)。 2 系统重要更新 2.1 将小组件添加到桌面 速览提醒事项和临近日程等。按住Control键点…

(一) 使用 Hugo 搭建个人博客保姆级教程(上篇)

手把手教你如何从0开始构建一个静态网站&#xff0c;这不需要有太多的编程和开发经验和时间投入&#xff0c;也基本不需要多少成本&#xff08;除了个性化域名&#xff09;&#xff0c;使用GitHub和Hugo模板即可快速构建和上线一个网站。 目标读者 本文档适用于以下用户&…

学信息系统项目管理师第4版系列16_资源管理过程

1. 组建项目团队&#xff0c;建设项目团队和管理项目团队属于执行过程组 1.1. 【高22上选21】 1.1.1. 【高21上选25】 1.2. 3版 2. 【高19上案三】 2.1. 【高18上案三】 2.2. 【高23上案一】 3. 规划资源管理 3.1. 定义如何估算、获取、管理和利用团队以及实物资源的过…

二叉树的顺序存储——堆——初识堆排序

前面我们学过可以把完全二叉树存入到顺序表中&#xff0c;然后利用完全二叉树的情缘关系&#xff0c;就可以通过数组下标来联系。 但是并不是把二叉树存入到数组中就是堆了&#xff0c;要看原原来的二叉树是否满足&#xff1a;所有的父都小于等于子&#xff0c;或者所有的父都…

十二、【漏洞复现】Rails任意文件读取(CVE-2019-5418)

十二、【漏洞复现】Rails任意文件读取(CVE-2019-5418&#xff09; 12.1、漏洞原理 Ruby on Rails是一个使用 Ruby 语言写的开源 Web 应用框架&#xff0c;它是严格按照 MVC 结构开发的。它努力使自身保持简单&#xff0c;来使实际的应用开发时的代码更少&#xff0c;使用最少…

JavaScript系列从入门到精通系列第十三篇:JavaScript中基本数据类型和引用数据类型,创建对象的两种方式

一&#xff1a;基本数据类型与引用数据类型 基本数据类型&#xff1a;String Number Boolean Null Undefined 引用数据类型&#xff1a;Object 我们的内存分成了两大块&#xff0c;一是栈内存二是堆内存。变量都是保存到栈内存中&#xff0c;var a 123; a和123都在栈空间&…

互联网Java工程师面试题·ZooKeeper 篇·第一弹

目录 1. ZooKeeper 面试题&#xff1f; 2. ZooKeeper 提供了什么&#xff1f; 3. Zookeeper 文件系统 4. ZAB 协议&#xff1f; 5. 四种类型的数据节点 Znode 6. Zookeeper Watcher 机制 -- 数据变更通知 7. 客户端注册 Watcher 实现 8. 服务端处理 Watcher 实现 9. 客…

Redis最常见的5种应用场景

Redis作为当今最流行的内存数据库&#xff0c;已经成为服务端加速的必备工具之一。对于Redis为什么那么快&#xff1f;以及Redis采用单线程&#xff0c;但为什么反而获得更高的性能的疑问&#xff0c;在之前的Redis为什么那么快&#xff1f;一文中&#xff0c;已经有所介绍。 …

TCP端口崩溃,msg:socket(): Too many open files

一、现象 linux系统中运行了一个TCP服务器&#xff0c;该服务器监听的TCP端口为10000。但是长时间运行时发现该端口会崩溃&#xff0c;TCP客户端连接该端口会失败&#xff1a; 可以看到进行三次握手时&#xff0c;TCP客户端向该TCP服务器的10000端口发送了SYN报文&#xff0c;…

Qt 综合练习小项目--反金币(1/2)

目录 1 项目简介 2 项目基本配置 2.1 创建项目 2.2 添加资源 3 主场景 3.1 设置游戏主场景配置 3.2 设置背景图片 3.3 创建开始按钮 3.4 开始按钮跳跃特效实现 3.5 创建选择关卡场景 3.6 点击开始按钮进入选择关卡场景 1 项目简介 翻金币项目是一款经典的益智类游戏…

开发过程教学——交友小程序

交友小程序 1. 我的基本信息2. 我的人脉2.1 我的关注2.2 我的粉丝 3. 我的视频4. 我的相册 特别注意&#xff1a;由于小程序分包限制2M以内&#xff0c;所以要注意图片和视频的处理。 1. 我的基本信息 数据库表&#xff1a; 我的基本信息我的登录退出记录我的登录状态&#x…

roarctf_2019_easy_pwn

roarctf_2019_easy_pwn Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled64位&#xff0c;保护全开 __int64 ADD() {__int64 result; // raxint i; // [rsp4h] [rbp-1Ch]int v2; // [rsp8h] [rbp-18h]int…