首页 > 其他分享 >深入理解synchronized的原理是什么

深入理解synchronized的原理是什么

时间:2024-09-07 11:25:39浏览次数:13  
标签:synchronized 对象 CAS 线程 深入 操作 如果 原理

对象头锁机制原则

在这里插入图片描述

Synchronized 的原理是什么

  Synchronized 是由JVM实现的一种实现互斥同步的实现方式。如果查看synchronized关键字修饰的字节码,会发现在编译器生成了monitorenter和monitorexit两个字节码指令。
在这里插入图片描述
  这两个指令的意思就是在虚拟机执行到monitorenter指令的时候,首先尝试获取到对象锁,对象头这部分在对象的最前端,包含两部分或者三部分:Mark Words、Klass Words,如果对象是一个数组,那么还可能包含第三部分:数组的长度。Mark Word需要重点说一下,这里面主要包含对象的哈希值、年龄分代、锁标志位等,大小为32位或64位
在这里插入图片描述
  如果这个对象没有锁定,或者当前线程已经拥有了这个对象锁,那么就把锁的计数器进行+1操作,当执行monitorexit指令的时候将锁计数器进行-1操作,当计数器为0的时候,锁就被释放了。
  如果获取对象失败了,当前线程就要阻塞等待,直到对象锁被另外一个线程释放。

刚刚提到的对象所,这个锁到底是什么?如何确定对象的锁?

  锁 的本质是monitorenter和monitorexit 字节码指令的一个Reference类型的参数,也就是是要锁定和解锁的对象。
  Synchronized可以修饰不同的对象,所以对应的对象锁可以通过如下的方式确定

  • 1、如果Synchronized明确指定了锁定对象,说明加锁对象为该对象。
  • 2、如果没有明确指定:
     如果Synchronized修饰的方法为非静态方法,表示此方法对应的对象为锁对象;
     如果Synchronized修饰的方法为静态方法,则表示这个方法对应的类对象为锁对象。

  需要注意的是,当一个对象被锁住的时候,对象里面所有用synchronized修饰的方法都将会被阻塞,而对象里非synchronized修饰方法可以正常被调用,不受到影响。

什么是可重入性,为什么synchronized是可重入锁?

  可重入性是锁的基本要求,是为了解决死锁的情况发生。
在这里插入图片描述
  如图所示,在一个类中同步调用了另一个同步方法,假如synchronized不支持重入,进入method2的时候当前线程获取锁,method2中又执行了method1的时候当前线程又要尝试获取锁,这个时候如果不支持重入,那就要等待释放,所以自己获取锁,自己等释放,就会导致死锁。

  对于synchronized来说,重要性是显而易见的。在执行monitorenter指令的时候 ,如果这个对象没有锁定,或者当前线程已经拥有了这对象锁,就把锁的计数器加一,实际上就是通过这种机制来实现锁重入。

JVM对于Java的原生锁做了那些优化

  在Java6之前Monitor实现完全依赖底层操作系统的互斥锁来实现,也就是在上面提到的获取/释放锁的逻辑。

  由于Java层面的线程与操作系统的原生线程有对应的映射关系,如果要将一个线程进行阻塞或者唤醒都需要操作系统线程的协助,也就是从用户态到内核态的切换,这种相互之间的切换其实是一件非常消耗内存的事情。

  所以JDK对于这种操作进行了很多的优化。

 使用自旋锁,也就是说把线程进行阻塞操作之前让线程自旋等待一段时间,可能在等待的时候其他线程已经解锁了,这个时候就不需要线程在等待了,避免了用户态到内核态的切换。

 在JDK中提供了三种不同的Monitor的实现,也就是三种不同的锁机制。

  • 偏向锁
  • 轻量级锁
  • 重量级锁

  这三种锁机制使得JDK对于synchronized的优化进行进一步的提升,当JVM检测到不同的资源竞争状况的时候,会自动切换到合适的锁实现机制,也就是所谓的锁的升级与降级。

  当没有出现竞争的时候,默认使用的是偏向锁。

  JVM会利用CAS操作,在对象头的MarkWord部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,因为在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。

  如果有一个线程试图锁定偏向锁对象的时候,JVM就撤销偏向锁,切换到轻量级锁。

  轻量级锁通过CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁,否则就升级为重量级锁。

为什么说synchronized是非公平锁?

  非公平主要表现在获取锁的行为上,并非按照申请锁的时间前后给等待线程分配锁,每当锁被释放之后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是会产生线饥饿现象。

什么是锁消除和锁粗化?

  • 锁消除:指虚拟机及时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。主要根据逃逸分析。开发者怎么会在明知道不会存在数据竞争的情况下使用同步操作呢?很多的操作其实不是开发者自己加入的。
  • 锁粗化:原则上,同步块的作用范围要尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。锁粗化就是增加锁的作用域。

为什么说 synchronized是一个悲观锁?乐观锁的实现原理又是什么?什么是CAS,它有什么特性?

  synchronized是一个悲观锁,因为他的并发策略是悲观的,不管是否会产生竞争,任何的数据都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

  随着硬件指令集的发展,可以使用基于冲突检测的乐观锁并发策略。先进性操作,如果没有其他线程征用数据,那么就操作成功。

  如果共享数据有被用到,产生了冲突,那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。

  乐观锁的核心算法是CAS(Compareand Swap,比较交换),涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等的时候才将内存值修改新值。

  这样处理的逻辑是,首先检查某块内存的值是否跟之前读取过的值一样,如果不一样则表示期间此内存值已经被别的线程修改过了,舍弃本次操作,否则则说明在此期间没有其他线程对此内存值进行修改,就可以直接使用该值。

  CAS具有原子性,他的原子性有CPU硬件指令实现保证,即使用JNI调用Native方法调用C++编写的硬件指令,JDK中提供了Unsafe类执行这些操作。

乐观锁一定就是好的?

  乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它的缺点:

  • 1、乐观锁只能保证一个共享变量的原子性操作,如果多一个或者几个变量,乐观锁就有点力不从心了,但互斥锁能轻易地解决该问题,不管对象数量多少以及对象大小。
  • 2、长时间自旋操作可能导致开销较大,加入CAS长时间不成功而一直自旋,就会导致CPU消耗过大。
  • 3、ABA问题,CAS的核心思想是比较交换,如果在过程中判断逻辑不够严谨,就会导致在一个线程将数据从A改成B,在另一个线程使用的时候,该线程又将数据改回了A,这样的时候对于另一个线程其实并没有感知到B值的存在。其实在整个过程中这个值是被修改过的。这种情况对于运算依赖比较大的场景影响比较大。解决的思路就是引入版本机制。

标签:synchronized,对象,CAS,线程,深入,操作,如果,原理
From: https://blog.csdn.net/nihui123/article/details/141949693

相关文章

  • HTTP协议基本知识点:工作原理、http请求、响应、连接以及缓存机制
    目录一、HTTP概述二、HTTP的版本三、HTTP请求1.请求方法2.请求头四、HTTP响应1.状态码2.响应头五、HTTP持久连接六、缓存机制1.CacheControl2.ETag3.LastModified七、安全性八、使用场景总结 一、HTTP概述 全称:超文本传输协议(HyperTextTransferProtoco......
  • 深入理解动态内存(一):动态内存使用常见问题
    目录对NULL指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放⼀块动态开辟内存的⼀部分对同⼀块动态内存多次释放动态开辟内存忘记释放(内存泄漏)对NULL指针的解引用操作#include<stdio.h>#include<stdlib.h>intmain(){ int*p......
  • (14)DDS基本原理与FPGA实现
    基本概念DDS(DirectDigitalSynthesizer),即数字合成器,是一种把一系列数字信号通过D/A转换器转化成模拟信号的数字合成技术DDS的实现有两种方式:查表法和计算法,下面将主要介绍DDS查表法的FPGA实现查表法:预先在ROM中存放不同相位对应的幅度序列,通过相位累加器的输出对其进行寻址,经......
  • 深入浅出孪生神经网络,高效训练模型
    大家好,在深度学习领域,神经网络几乎能处理各种任务,但通常需要依赖于海量数据来达到最佳效果。然而,对于像面部识别和签名验证这类任务,我们不可能总是有大量的数据可用。由此产生了一种新型的神经网络架构,称为孪生网络。孪生神经网络能够基于少量数据实现精准预测,本文将介绍孪生......
  • 最近邻回归算法原理及Python实践
    最近邻回归算法(K-nearestneighborsregression,简称KNN回归)是一种简单而又直观的非参数回归方法。它基于这样一个思想:一个样本的输出值可以通过其最近的K个邻居的输出值的某种形式(如加权平均)来预测。以下是KNN回归算法的主要原理:一、基本步骤计算距离:对于给定的预测样本......
  • 决策树回归算法原理及Python实践
    决策树回归算法(DecisionTreeRegression)是一种常用的机器学习算法,用于预测连续型变量的取值。其原理基于树结构对数据进行建模和预测,通过将数据集划分为不同的区域,并在每个区域内预测一个常数值来实现回归任务。以下是决策树回归算法原理的详细解释:一、基本原理树结构:......
  • Android 开发避坑经验(2):深入理解Fragment与Activity交互
    在Android开发过程中,Fragment和Activity之间的交互是一个常见的难题,处理不当会引发UI更新问题、生命周期混乱、数据丢失等问题。这篇文章将深入探讨如何避免这些常见坑点,提供可靠的解决方案,并通过示例代码展示最佳实践。1.坑点:Fragment和Activity的生命周期差异......
  • 记录 PyQt6 / PySide 6 自定义边框窗口的 Bug 及可能可行的解决方案:窗口抖动和添加 DW
    前言:本篇文章将要讨论我在前不久发表的关于PyQt6/PySide6自定义边框窗口代码及内容中的问题:(终)PyQt6/PySide6+Pywin32自定义标题栏窗口+完全还原Windows原生窗口边框特效_pyside6win32无边框窗口-CSDN博客https://blog.csdn.net/2402_84665876/article/detail......
  • RAG与LLM原理及实践(14)---RAG Python 前端构建技术Flask
    目录背景Flask简介     Flask的特点flask安装Flaskpythonserver端处理app资源映射router概念Flask客户端处理Jinja2 概述具体语法实例python后端 代码前端相关代码代码解释运行check启动日志背景本专栏之前的文章都在描述RAG后台的......
  • Kafka的三高设计原理
    1.生产者缓存机制--高性能生产者缓存机制的主要目的是将消息打包,减少网络IO频率kafka生产者端存在消息累加器RecordAccumulator,它会对每个Partition维护一个双端队列,队列中消息到达一定数量后或者到达一定时间后,通过sender线程批量的将消息发送给kafka服务端。(批量发送)2.......