【JavaEE】synchronized原理

news2024/9/24 1:19:48

目录

前言

synchronized特性

synchronized特点

synchronize的加锁过程

1.无锁-->偏向锁

2.偏向锁->轻量级锁

3.轻量级锁->重量级锁

锁的优化操作

1.锁消除

2.锁粗化

3.自适应自旋锁

相关面试题

1.什么是偏向锁?

2.synchronized的实现原理是什么?


前言

前面我们学习了synchronized对应的锁策略,那么本篇我们就来深入学习一下synchronized原理。本篇后面也会讲解synchronized的一些相关面试题。

synchronized特性

synchronized有四个特性:原子性、可见性、可重入性、有序性。

  1. 原子性即对一个操作或者多个操作,要么全部执行并且执行过程不会被任何因素打断,要么就都不执行

在java中,对基本数据类型的变量的读取和赋值操作都是原子性的,这些操作不会被打断。但像i++,i-=1这类操作,就不是原子性的,是由读取、计算、赋值这三个指令构成的,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的就是脏数据,无法保证原子性。

被synchronized修饰的类或者对象的的所有操作都是原子的

注意:volatile关键字不具有原子性

     2.可见性指多个线程访问一个资源时,该资源的状态、信息等对于其他线程都是可见的

synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到内存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。

而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新到内存,内存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。

      3.可重入性:线程可对同一把锁加多次锁,可以再次获取锁而不会出现死锁。

synchronized和ReentrantLock都是可重入锁当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

      4.有序性:有序性值程序执行的顺序按照代码先后执行。 

synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。

总结一下synchronized的特点。

synchronized特点

  1. 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。
  2. 开始时轻量级锁实现,如果锁被持有时间太长,就转换为重量级锁,
  3. 实现轻量级锁的时候大概率用的是自旋锁策略
  4. 是一种不公平锁
  5. 是一种可重入锁
  6. 不是读写锁

synchronize的加锁过程

JVM将synchronized锁分为无锁、偏向锁、轻量级锁、重量级锁。会根据情况,进行依次升级。

synchronized在jdk6后进行了优化,锁升级的方向是不可逆的

1.无锁-->偏向锁

当我们使用synchronized进行加锁的时候,线程不会立即从无锁的状态转换为加锁的状态,而是会先处于一个偏向锁的状态。

什么是偏向锁?

偏向锁不是真的"加锁",只是给对象头中做⼀个"偏向锁的标记",记录这个锁属于哪个线程. 如果后续没有其他线程来竞争该锁,那么就不⽤进⾏其他同步操作了(避免了加锁解锁的开销);如果后续有线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了,很容易识别当前申请锁的线程是不是之前记录的线程),那就取消原来的偏向锁状态,进入一般的轻量级锁状态。

偏向锁本质上相当于"延迟加锁"能不加锁就不加锁,类似于懒汉模式,尽量来避免不必要的开销。但该做的标记还是得做,否则无法区分何时需要真正加锁。

2.偏向锁->轻量级锁

出现锁竞争后,持有偏向锁的线程就会转换成轻量级锁(自适应的自旋锁)。此处的轻量级锁就是通过CAS(简单理解就是一条指令就完成比较和交换)来实现的。

  • 通过CAS检查并更新一块内存(比如null=>该线程引用)
  • 如果更新成功,则认为加锁成功
  • 如果更新失败,则认为锁被占用,继续自旋式的等待(并不放弃CPU)

3.轻量级锁->重量级锁

如果锁竞争比较激烈,那么synchronized就会从轻量级锁转换成重量级锁(挂起等待锁)会使线程进入阻塞等待。

  • 执行加锁操作,先进入内核态
  • 在内核态判断当前锁是否被占用
  • 如果锁没有被占用,则加锁,并切换为用户态
  • 如果锁被占用,就会阻塞等待挂起,等待被唤醒。
  • 经历了⼀系列的沧海桑⽥,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒 这个线程,尝试重新获取锁.

锁的优化操作

除了上面提到的锁升级(锁膨胀)还有以下的优化技术。

1.锁消除

Java中锁消除是Java虚拟机(JVM)中的一种优化技术,用于消除不必要的同步锁,从而提高程序的性能。

synchronized关键字能够实现同步和互斥,确保多个线程对共享资源访问的一致性。但synchronized也会有一定的开销,频繁的加锁,也会降低程序的性能,包括获取锁、执行同步代码块、释放锁等操作所产生的时间成本,也可能导致线程阻塞和上下文切换的代价。

为了减少synchronized带来的开销,JVM对synchronized进行了锁消除的优化技术。

锁消除原理:在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而提高程序性能的目的。例如在单线程的情况下,使用StringBuffer的append方法、Vector的add方法等,JVM会进行锁消除优化。

示例:

class Demotest{
    // 使用静态变量count来记录累加的和,静态变量被所有实例共享
    static int count=0;
    /**
     * 主函数执行累加操作
     * @param args 命令行参数,本例中未使用
     */
    public static void main(String[] args) {
        // 循环100次进行累加操作
        for(int i=0;i<100;i++){
            // 使用类对象作为锁进行同步控制,确保线程安全
            synchronized (Demotest.class){
                count+=i;
            }
        };
        // 输出最终的累加结果
        System.out.println(count);
    }
}

对于上述这段代码中,由于没有其他线程与主线程进行锁竞争,因此JVM可以安全地消除掉该代码块的同步锁操作,提高程序性能。

在高并发环境下,还是有必要使用synchronized来使线程对共享资源访问的正确性和一致性。锁消除只是一种优化技术,不能保证在所有情况下都能消除同步锁操作。但也不能无脑加锁。

注意:锁消除的优化是在编译阶段进行的操作,而偏向锁则是在代码运行过程中完成的。

2.锁粗化

锁的粒度:锁的粒度是指在并发编程中锁的作用范围或者说是锁保护的数据结构的大小。分为粗和细。锁的粒度越细,程序的并发性能就越好,但带来的锁竞争和开销就越多。

通常情况下,为了多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但在某些情况下,一个程序对同一个锁不间断的请求、同步和释放,那么就会消耗一定的系统资源。所以我们就可以通过锁粗化,将多个锁请求合并成一个,避免短时间内大量请求。锁粗化就是将多个锁请求合并成一个请求,以降低短时间内大量的锁请求、同步和释放带来的性能损耗

锁粗化其实就是将多个“细粒度"的锁合并为一个“粗粒度”的锁

实际开发过程中,使⽤细粒度锁,是期望释放锁的时候其他线程能使⽤锁. 但是实际上可能并没有其他线程来抢占这个锁.这种情况JVM就会⾃动把锁粗化,避免频繁申请释放锁

示例:

class Demoks{
    static int count=0;
    public static void main(String[] args) {
        for(int i=0;i<1000;i++){
            synchronized (Demotest.class){
                count+=i;
            }
        }
        System.out.println(count);
    }
}

对于上述代码中,我们可以看到,每次循环都需要进行加锁释放锁,这样带来的性能损耗就比较大,所以我们可以将锁粗化,将多个锁合并成一个。即:

class Demoks{
    static int count=0;
    public static void main(String[] args) {
        synchronized (Demotest.class) {
            for (int i = 0; i < 1000; i++) {
                count += i;
            }
        }
        System.out.println(count);
    }
}

这样就可以有效地避免资源浪费。

当然,不是任何情况下都能进行锁粗化的优化操作,我们需要保证锁粗化的结执行果是正确情况下代码的执行结果。

3.自适应自旋锁

自旋锁在上一篇我们已经讲过,其实就是在获取不到锁的时候,不会释放CPU资源,而是会一直循环尝试获取到锁,直到获取到锁为止的策略。伪代码如下:

//尝试获取锁
while(!isLock()){

   
}

自旋锁的优点在于避免了一些线程的挂起和恢复操作,这两操作都是需要从用户态转入内核态的,过程较慢,但自旋锁是在用户态的,通过自旋的方式在一定程度上能避免线程挂起和恢复造成的性能开销。 

但是长时间自旋还获取不到锁,就会造成一定的资源浪费,所以我们会给自旋设置一个固定的值来避免一直自旋的性能开销。对于synchronized来说,它的自旋锁是自适应自旋锁,在jdk1.6的优化后,就引入了自适应的自旋锁。

自适应自旋锁:线程自旋的次数不再是固定的值,而是一个动态变化的值,这个值会根据你上一次自旋获取锁的状态来决定自旋的次数,例如,上一次通过自旋成功获取到了锁,那么这次通过自旋也有可能会获取到锁,所以这次自旋的次数就会增多一些,而如果上一次通过自旋没有成功获取到锁,那么这次自旋可能也获取不到锁,所以为了避免资源的浪费,就会少循环或者不循环,以提高程序的执行效率。简单来说,如果线程自旋成功了,则下次自旋的次数会增多,如果失败,下次自旋的次数会减少。


相关面试题

1.什么是偏向锁?

偏向锁不是真的加锁,而是在锁的对象头中记录一个标记(记录该锁所属的线程),如果没有其他线程参与锁竞争,那么就不会真正执行加锁操作,从而降低程序开销。一旦真的涉及到其他的线程竞争,再取消偏向锁状态,进入轻量级锁状态。

2.synchronized的实现原理是什么?

本篇内容。


以上就是本篇所有内容,若有不足,欢迎指正~

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

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

相关文章

LVS原理及实例

目录 LVS原理 LVS概念 lvs集群的类型 lvs-nat 解释 传输过程 lvs-dr 解释 传输过程 特点 lvs-tun LVS&#xff08;Linux Virtual Server&#xff09;常见的调度算法 防火墙标记&#xff08;Firewall Marking&#xff09;结合轮询调度 实战案例 lvs的nat模式配置 …

代码随想录算法刷题训练营day49:LeetCode(42)接雨水、LeetCode(84)柱状图中最大的矩形

代码随想录算法刷题训练营day49&#xff1a;LeetCode(42)接雨水、LeetCode(84)柱状图中最大的矩形 LeetCode(42)接雨水 题目 代码 import java.util.Stack;class Solution {public int trap(int[] height) {//用单调栈进行操作int sum0;Stack<Integer> stacknew Stac…

计算机的错误计算(五十六)

摘要 展示大数的正切函数值的错误计算。 由计算机的错误计算&#xff08;五十五&#xff09;知&#xff0c;国际IEEE 754 标准给出的正切函数的定义域是整个实数域范围。那么&#xff0c;在该范围内&#xff0c;软件的计算效果如何呢&#xff1f; 例1. 计算 . 在 Python下计…

字体识别验证码的介绍!

字体识别验证码 ​是一种安全机制&#xff0c;‌通过要求用户识别特定字体来验证用户的身份或防止自动化攻击。‌这种验证码通常包含一些经过特殊设计的字符&#xff0c;‌需要用户根据这些字符的特定样式&#xff08;‌如字体、‌字形等&#xff09;‌来进行识别和输入。‌字…

html+css网页制作 博云丝网5个页面 无js ui还原度100%

htmlcss网页制作 博云丝网5个页面 无js ui还原度100% 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取…

el-tree限制选中个数

el-tree限制选中个数 <el-treestyle"max-width: 600px":data"Treedata":check-strictly"true"show-checkboxnode-key"id":props"defaultProps":default-expanded-keys"[1, 2]"ref"treeRef"check&quo…

Java数组类型

目录 一维数组 一维数组的声明 动态数组初始化 静态数组的初始化 一维数组的访问 数组长度 数组的遍历操作 数组中的默认值 数组中的两个常见异常 越界访问异常ArrayIndexOutOfBoundsException 空指针异常NullPointerException Java中的内存划分 一维数组的内存分…

pdf怎么加密码怎么设置密码?pdf加密码的几种设置方法

在数字化时代&#xff0c;信息的保密性与安全性日益成为我们不可忽视的重要环节。尤其对于包含敏感信息或个人隐私的PDF文档而言&#xff0c;保护其免受未授权访问的侵扰显得尤为重要。通过为PDF文档设置密码保护&#xff0c;我们能够筑起一道坚实的防线&#xff0c;确保只有拥…

危化品安全生产风险监测预警系统的构建与实施

引言 1、背景与重要性 在现代工业生产中&#xff0c;危险化学品&#xff08;简称“危化品”&#xff09;的使用和管理日益广泛。它们在化工、制药、能源等多个领域中扮演着不可或缺的角色。然而&#xff0c;危化品因其固有的易燃、易爆、腐蚀、有毒等特性&#xff0c;一旦管理…

Git使用错误分析

一.fatal: Pathspec is in submodule 我做了这样的错误操作&#xff0c;在一个仓库下的一个子目录&#xff0c;执行了git init 创建了一个子仓库&#xff0c;然后想删掉这个子仓库&#xff0c;就只删除了该子目录下的.git文件夹&#xff0c;而没有删除缓存&#xff0c;执行如下…

java: Internal error in the mapping processor: java.lang.NullPointerExceptio

java: Internal error in the mapping processor: java.lang.NullPointerExceptio 解决办法&#xff1a;idea里面加参数-Djps.track.ap.dependenciesfalse即可

Java程序设计:Java 网络聊天室客户端

相关网络编程前两篇文章&#xff1a;Java程序设计&#xff1a;Java网络编程实验 服务端部分见上一篇文章&#xff1a;Java程序设计&#xff1a;Java网络聊天室服务端 目录 1 实验名称 2 实验目的 3 实验源代码 4 实验运行结果图 5 总结 1 实验名称 Java 网络聊天室客户端 …

纳米软件的电源模块测试系统有什么功能和优势?

纳米软件电源模块自动化测试系统主要实现针对单路进4路出和单路进2路出的非隔离DCDC电源模块进行测试。本次方案包含对以下15个测试项目的自动化测试&#xff0c;分别为输入电压范围VIN的最大值最小值、输出电压范围VOUT的最大值最小值、输出纹波VOPP、电压调整率SV、负载调整率…

基于SpringBoot+Vue的学院商铺管理系统(带1w+文档)

基于SpringBootVue的学院商铺管理系统(带1w文档) 基于SpringBootVue的学院商铺管理系统(带1w文档) 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在…

[VBA]使用VBA在Excel中 操作 形状shape 对象

excel已关闭地图插件,对于想做 地图可视化 的,用形状来操作是一种办法,就是要自行找到合适的 地图形状,修改形状颜色等就可以用于 可视化展示不同省市销量、人口等数据。 引言 在Excel中,通过VBA(Visual Basic for Applications)可以极大地增强数据可视化和报告自动化…

Spring Cache在业务系统中最佳实践教程详解及实现原理

1.概述 接着之前总结的如何保证MySQL与Redis数据同步一致性一文中提到在业务代码中一般采用旁路缓存策略方式实现同步&#xff0c;Spring Cache 就是 Spring Framework 基于该策略方式提供的一种缓存抽象&#xff0c;可以帮助开发者简化缓存的使用过程。它支持多种缓存实现&am…

【STM32】DMA数据转运(存储器到存储器)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 DMA简介 DMA时钟使能 DMA初始化 转运起始和终止的地址 转运方向 数据宽度 传输次数 转运触发方式 转运模式 通道优先级 开启DMA通道 DMA初始化框架 更改转运次数 DMA应用实例-…

Google Mock 和 Google Test编写单元测试入门(环境配置、简单执行)

文章目录 环境的配置方法1&#xff1a;从源代码构建第一步&#xff1a;克隆库的源代码第二步&#xff1a;构建库 方法 2&#xff1a;使用 CMake 的 FetchContent示例 CMakeLists.txt 项目的创建项目结构CMakeLists.txt (根目录)main.cpp (示例程序)tests/CMakeLists.txt (测试部…

更换CentOS中docker的镜像源

如果docker pull镜像出现&#xff1a; error pulling image configuration: download failed after attempts6: dial tcp 173.236.182.137:443: i/o timeout 如果是阿里云&#xff0c;我们进入阿里云官网&#xff1a; 阿里云开发者社区-云计算社区-阿里云 然后点击产品&#…

医疗器械重大网络安全更新是什么?有何价值?

医疗器械重大网络安全更新是指那些影响到医疗器械的安全性或有效性的网络安全更新。这类更新通常涉及重大网络安全功能的变更&#xff0c;旨在提升医疗器械在网络环境中的安全性和稳定性。 一、医疗器械重大网络安全更新的价值 保障患者安全&#xff1a;医疗器械在处理患者数据…