Java synchronized底层实现原理以及锁优化

Java中的synchronized关键字用于保证同步访问,避免出现多线程并发访问共享资源的问题,保证程序的正确性和一致性。在JVM中,synchronized的实现原理是通过Java对象头中的一个有关锁的标识位来实现的,具体的底层实现原理如下:

Java对象头

Java对象在堆中的数据结构是由对象头和实例数据两部分组成的,其中对象头占用了8个或者12个字节(64位JVM为12字节,32位JVM为8字节)。JVM对象头中主要包含了两部分信息:对象的hashCode以及对象的锁信息,锁信息主要有以下内容:

  • Mark Word:存储对象的标志位信息,主要有锁状态及一些标志位;
  • Klass Pointer:指向该对象对应的类元信息指针。

synchronized的实现原理

synchronized 关键字可以用来修饰普通方法、静态方法和代码块,这里主要讲解普通方法的synchronized实现原理。

普通方法的synchronized底层实现主要包含四个步骤:

  1. 当线程A来执行synchronized修饰的方法时,首先会检查该方法的锁状态是否处于 ''Unlocked" 状态;
  2. 如果处于 ''Unlocked" 状态,线程A则会获得该方法所对应对象的锁并将锁的状态设置为 ''Locked";
  3. 如果处于 ''Locked" 状态,线程A就会进入同步队列(等待队列);因为该方法是被synchronized修饰的,只能被一个线程执行,因此如果有多个线程来调用该方法,则会进入同步队列中阻塞等待执行;
  4. 当同步队列中线程的锁状态被释放时,唤醒队列中的线程,然后其中一条线程再次尝试获取该方法所对应对象的锁并将锁的状态设置为 ''Locked",取得锁的线程进入执行状态,其他线程仍然进入同步队列中等待。

锁优化

由于synchronized在实现同步的时候会带来一定的系统开销和调度开销,因此,JVM提供了一些锁优化来提高程序的执行效率,其中包括:

自旋锁

自旋锁是通过循环CAS(Compare-And-Swap)操作实现的,当线程发现自己要获取的锁已经被其他线程获取时,它不会马上进入阻塞状态,而是采用循环的方式不断尝试获取锁(重试),直到获取锁成功或者达到一定重试次数为止。自旋锁适用于锁竞争情况不是很激烈的场景。

锁消除

在程序执行过程中,如果JVM发现某些锁不会被多线程访问,那么JVM就会自动把锁消除掉,从而避免不必要的锁竞争。

例如:

public String getString(String str1, String str2) {
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(str1).append(str2);
    return stringBuffer.toString();
}

上述代码中,由于StringBuffer对象只会在当前方法中使用,并不会被其他的线程访问,因此JVM可以自动把该方法中的锁消除掉,从而提高方法的执行效率。

轻量级锁

轻量级锁是为了避免线程在执行同步代码块时频繁地进入阻塞状态而引入的一种优化方式。JVM使用CAS操作,把当前线程的锁对象的MarkWord复制到线程的本地变量中,然后再尝试用CAS操作将线程本地变量的MarkWord替换回原对象的MarkWord。

偏向锁

偏向锁是JVM提供的一种轻量级锁优化策略,它适用于哪些只会有一个线程访问的情况。偏向锁的核心思想是:如果一个线程在访问一个锁对象之前,该锁对象没有被其他线程访问过,那么该线程就可以获得该锁,并把MarkWord中的偏向锁标识符设置为该线程的唯一标识符。这样,如果以后再有该线程访问该锁对象,那么该线程就可以直接获取锁,而不再需要竞争。如果其他线程要访问该锁对象,则会先调用CAS操作来检测当前锁标识符是否等于线程的唯一标识符,如果是,则获取锁成功,否则锁标识符被升级为轻量级锁。

例如:

public class Test {
    public static void main(String[] args) {
        synchronized (Test.class) {
            synchronized (Test.class) {
                System.out.println("Nested Lock!");
            }
        }
    }
}

在上述代码中,当线程A第一次执行synchronized块时,会尝试获取Test.class的锁并将该锁的状态设置为 ''Locked",然后线程A对应的锁标识符会被设置为A的特定标识符。当线程A第二次执行synchronized块时,会检测Test.class的锁标识符是否等于线程A的特定标识符,如果等于,则获取锁成功,否则尝试CAS操作将该锁的状态从 ''Unlocked" 修改为 ''Locked";如果修改成功,则锁标识符被设置为A的特定标识符,获取锁成功,否则线程A会将该锁升级为轻量级锁。

综述: 以上就是Java synchronized的底层实现原理以及锁优化的攻略,通过深入了解synchronized关键字的底层实现原理以及锁的优化机制,可以使我们在程序开发过程中更加高效地使用多线程技术,提高程序的性能。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java synchronized底层实现原理以及锁优化 - Python技术站

(0)
上一篇 2023年5月26日
下一篇 2023年5月26日

相关文章

  • 使用java生成json时产生栈溢出错误问题及解决方案

    使用Java生成JSON时如果数据量较大、层次较深,容易出现栈溢出错误。本文将介绍栈溢出的原因及两种解决方案。 问题原因 生成JSON时,Java使用递归方式遍历数据结构,将其转换为JSON格式。如果数据量很大,层次较深,那么递归将产生很多层次的调用,导致栈空间不足,产生栈溢出错误。 解决方案1:调整栈空间大小 Java虚拟机中,栈大小默认为1MB,可通过设…

    Java 2023年5月20日
    00
  • Spring Boot使用FastJson解析JSON数据的方法

    Spring Boot使用FastJson解析JSON数据的方法 介绍 FastJson是一个Java语言编写的高性能JSON处理器,它是阿里巴巴开源的项目,相比其他的JSON处理器,FastJson具有非常快的序列化和反序列化速度,在大数据量的情况下性能表现优异,被广泛应用于阿里巴巴的各项业务和产品中。 Spring Boot是一个基于Spring框架的快…

    Java 2023年5月26日
    00
  • Java util concurrent及基本线程原理简介

    Java util concurrent及基本线程原理简介 线程基本概念 线程是操作系统进行任务调度和执行的基本单位,一个进程可以拥有多个线程。 线程是轻量级的,相对于进程来说占用较少的资源。 线程也是并发编程的基石,不同的线程可以同时执行不同的任务,提高了应用程序的并发性。 线程的状态 新建状态 线程是尚未启动的状态,实例化了一个Thread对象,还未调用…

    Java 2023年5月18日
    00
  • springboot 2.3之后消失的hibernate-validator解决方法

    下面是详细的攻略: 问题背景 在Spring Boot 2.3版本之后,引入了一个新的starter库,名为validation-starter,用于提供Java Bean的数据校验功能。同时,hibernate-validator也被移出了Spring Boot的核心依赖,这导致运行时找不到这个库,会报出ClassNotFoundException的错误。…

    Java 2023年5月20日
    00
  • Java对象的内存布局全流程

    Java对象的内存布局是指Java对象在内存中的存储结构,其包含了对象头、实例数据以及对齐填充三个部分。这个过程可以用以下五个步骤来描述: 虚拟机中的对象是如何创建的? 在JVM中,当我们通过new关键字创建一个Java对象时,JVM会在堆内存中为该对象分配一块内存空间,并返回该对象的引用。对象在内存中的存储结构如下所示: Memory |———…

    Java 2023年5月26日
    00
  • SpringBoot整合Security权限控制登录首页

    下面我将详细讲解“SpringBoot整合Security权限控制登录首页”的完整攻略,并给出两个示例来帮助理解。 一、准备工作 1.1 引入依赖 首先,我们需要在pom.xml文件中引入相关依赖: <!– Spring Security依赖 –> <dependency> <groupId>org.springfra…

    Java 2023年5月20日
    00
  • Mybatis与Jpa的区别和性能对比总结

    Mybatis与JPA的区别 定义 MyBatis是一个开源的ORM框架,它支持定制化SQL、存储过程以及高级映射。同时提供了缓存机制,可以优化数据库访问性能。 而JPA(Java Persistence API)是一个规范,不是具体的实现。它基于ORM(Object-Relational Mapping,对象关系映射)思想,将数据库中的表映射成Java对象…

    Java 2023年5月20日
    00
  • 原生JS实现$.param() 函数的方法

    当我们使用jQuery库时,我们通常使用$.param()函数来将一个对象序列化为一个字符串形式的参数列表,以便可以在URL,Ajax请求等中使用。但是如果我们需要在没有引入jQuery的情况下使用该函数,我们可以考虑使用原生JS来实现。 下面是用原生JS实现$.param()函数的方法: 1. 将一个对象序列化为查询字符串 将一个对象序列化为查询字符串的方…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部