一文彻底搞懂synchronized实现原理

文章目录

  • 1. synchronized 是什么
  • 2. synchronized 可以实现的锁
  • 3. synchronized 使用
  • 4. synchronized 底层原理
    • 4.1 作用于同步代码块
    • 4.2 作用于方法

1. synchronized 是什么

synchronized 是 Java 中实现线程同步的关键字,用于保护共享资源的访问,确保在多线程环境中同一时间只有一个线程能够访问特定的代码段或方法。它提供了互斥性、可见性和有序性三个基本特性,确保了线程间操作的原子性和数据的一致性。

  • 互斥性: 当一个线程进入了 synchronized 代码块或方法时,其他试图进入该同步区域的线程必须等待,直至拥有锁的线程执行完毕并释放锁。这确保了在同一时间只能有一个线程访问共享资源,避免了竞态条件和数据不一致的问题。

  • 可见性: synchronized 关键字还确保了线程间的数据可见性。一旦一个线程在 synchronized 块中修改了共享变量的值,其他随后进入同步区域的线程可以看到这个更改。这是因为解锁过程会将工作内存中的最新值刷新回主内存,而加锁过程则会强制从主内存中重新加载变量的值。

  • 有序性: synchronized 提供的第三个特性是有序性,它可以确保在多线程环境下,对于同一个锁的解锁操作总是先行于随后对同一个锁的加锁操作。这就建立了线程之间的内存操作顺序关系,有效地解决了由于编译器和处理器优化可能带来的指令重排序问题。

2. synchronized 可以实现的锁

  • 可重入锁(Reentrant Lock): synchronized 实现的锁是可重入的,允许同一个线程多次获取同一个锁,而不会造成死锁。这意味着线程在持有锁的情况下可以再次获取相同的锁,而不会被阻塞。

  • 排它锁/互斥锁/独占锁: synchronized 实现的锁是排它的,也就是说,在任意时刻只有一个线程能够获取到锁,其他线程必须等待该线程释放锁才能继续执行。这确保了同一时刻只有一个线程可以访问被锁定的代码块或方法,从而保证了数据的一致性和完整性。

  • 悲观锁: synchronized 实现的锁属于悲观锁,因为它默认情况下假设会发生竞争,并且会导致其他线程阻塞,直到持有锁的线程释放锁。悲观锁的特点是对并发访问持保守态度,认为会有其他线程来竞争共享资源,因此在访问共享资源之前会先获取锁。

  • 非公平锁: 在早期的 Java 版本中,默认实现的 synchronized 是非公平锁,也就是说,线程获取锁的顺序并不一定按照它们请求锁的顺序来进行,而是允许“插队”,即已经在等待队列中的线程可能被后来请求锁的线程抢占。

3. synchronized 使用

synchronized关键字可以修饰方法、代码块或静态方法,用于确保同一时间只有一个线程可以访问被synchronized修饰的代码片段。

  • 修饰实例方法: 当synchronized修饰实例方法时,锁住的是当前实例对象(this)。这意味着在同一时刻,只能有一个线程访问此方法,其他线程需要等待当前线程执行完毕才能执行该方法。修饰实例方法的方式可以确保对实例变量的访问是线程安全的。
public synchronized void methodName() {
    // synchronized 代码块
}
  • 修饰静态方法: 当synchronized修饰静态方法时,锁住的是类的Class对象。因此,无论多少个该类的实例存在,同一时刻也只有一个线程能够访问此静态同步方法。这种方式可以确保对静态变量的访问是线程安全的。
public static synchronized void staticMethodName() {
    // synchronized 代码块
}
  • 修饰代码块: 使用 synchronized 关键字修饰一个代码块,将需要同步的代码包裹在 synchronized 关键字所修饰的代码块中。通过指定对象作为锁,可以更精确地控制同步范围,只有持有该对象锁的线程才能执行被synchronized修饰的代码块。
synchronized (obj) {
    // 需要同步的代码块
}

synchronized关键字确保了对共享资源的访问是线程安全的。但过多地使用synchronized可能会导致性能问题,因此在设计并发程序时需要权衡考虑。

4. synchronized 底层原理

synchronized 的底层原理涉及到 Java 对象头(Object Header)和 Monitor(监视器)两个关键概念。
在这里插入图片描述

  • Java 对象头:
    在 Java 虚拟机中,每个对象都有一个对象头,用于存储对象的元数据信息,包括对象的哈希码、GC 相关信息、锁状态等。对象头通常包含一个标记字段(Mark Word),用于标识对象的锁状态。

  • Monitor(监视器):
    Monitor 是一种同步机制,负责管理对象的锁。每个对象都与一个 Monitor 相关联。当一个线程尝试进入一个被synchronized修饰的代码块或方法时,它会尝试获取对象的 Monitor。如果 Monitor 处于无锁状态,则当前线程会尝试将其锁定;如果 Monitor 已经被其他线程锁定,则当前线程会进入阻塞状态,直到持有锁的线程释放锁。

在 JDK 1.6 之前,synchronized 关键字的实现确实被认为是重量级锁。其原理基于操作系统提供的互斥量(Mutexes)来实现线程间的同步,这涉及了从用户态到内核态的切换以及线程上下文切换等相对昂贵的操作。一旦一个线程获得了锁,其他试图获取相同锁的线程将会被阻塞,这种阻塞操作会导致线程状态的改变和 CPU 资源的消耗。

具体而言,在 JDK 1.6 之前,synchronized 的实现过程大致如下:

  • 当线程尝试进入 synchronized 代码块时,它会尝试获取对象的 Monitor(监视器)。
  • 如果 Monitor 的锁状态是无锁状态(Unlocked),则当前线程将尝试获取锁。如果获取成功,则进入临界区执行代码。
  • 如果锁已经被其他线程持有,则当前线程将进入阻塞状态,等待锁被释放。
  • 当持有锁的线程退出临界区并释放锁时,等待的线程将被唤醒,重新尝试获取锁。

这种实现方式存在以下问题:

  • 需要从用户态到内核态的切换,以及线程上下文的切换,导致性能开销较大。
  • 对于竞争不激烈的情况,阻塞等待锁的线程可能会浪费大量的 CPU 时间。
  • 线程在竞争锁的过程中可能会发生多次上下文切换,影响性能。
  • 因此,在高并发、低锁竞争的情况下,早期版本的 synchronized 实现可能成为性能瓶颈。

为了解决这些问题,在 JDK 1.6 中引入了偏向锁(Biased Locking)和轻量级锁(Lightweight Locking)等优化机制,提高了 synchronized 的性能和并发能力。

底层实现机制如下:

  • 偏向锁(Bias Locking):当一个线程第一次访问一个同步块时,它会尝试获取对象的偏向锁。如果对象没有被其他线程锁定,那么当前线程会尝试将对象的偏向锁设置为自己。之后,当该线程再次访问同步块时,不需要再次获取锁,直接进入同步块执行。这样可以提高同步操作的性能,减少不必要的竞争。

  • 轻量级锁(Lightweight Locking):如果对象已经被其他线程获取了偏向锁,但当前线程又想要获取锁,就会升级为轻量级锁。轻量级锁的获取过程包括尝试将对象头的 Mark Word 设置为指向当前线程的锁记录(Lock Record)和CAS(Compare and Swap)操作。如果锁竞争激烈,会升级为重量级锁。

  • 重量级锁(Heavyweight Locking):如果对象的锁被多个线程竞争,那么会升级为重量级锁。重量级锁使用操作系统的互斥量(Mutex)来实现同步,涉及到用户态和内核态的切换,性能相对较低,但能够确保线程的互斥访问。

4.1 作用于同步代码块

当synchronized关键字作用于同步代码块时,Java 虚拟机使用 monitorenter 和 monitorexit 指令来实现同步。具体的工作流程如下:

  • 当线程进入同步代码块时,首先执行 monitorenter 指令,该指令尝试获取对象的 Monitor(即锁)。
  • 如果对象的 Monitor 处于无锁状态,则当前线程将成功获取锁,并继续执行同步代码块中的逻辑;同时,Monitor 的拥有者标记为当前线程。
  • 如果对象的 Monitor 已经被其他线程锁定,则当前线程将进入阻塞状态,直到获取到锁为止。
  • 当线程退出同步代码块时,执行 monitorexit 指令,释放对象的 Monitor,并清空拥有者标记。

4.2 作用于方法

而当synchronized关键字作用于方法时,Java 虚拟机会通过在方法的访问标志中设置 ACC_SYNCHRONIZED 标志来实现方法级别的同步。具体的工作流程如下:

  • 当线程调用同步方法时,首先尝试获取方法所属对象的 Monitor。
  • 如果方法所属对象的 Monitor 处于无锁状态,则当前线程将成功获取锁,并执行方法中的逻辑;同时,Monitor 的拥有者标记为当前线程。
  • 如果方法所属对象的 Monitor 已经被其他线程锁定,则当前线程将进入阻塞状态,直到获取到锁为止。
  • 当方法执行结束时,释放方法所属对象的 Monitor,并清空拥有者标记。
  • 需要注意的是,对于静态同步方法,Java 虚拟机会使用类的 Class 对象作为 Monitor,而对于实例同步方法,则会使用实例对象的 Monitor。这样可以确保静态方法和实例方法之间的互斥性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP/IP协议、HTTP协议和FTP协议等网络协议包简介

文章目录 一、常见的网络协议二、TCP/IP协议1、TCP/IP协议模型被划分为四个层次2、TCP/IP五层模型3、TCP/IP七层模型 三、FTP网络协议四、Http网络协议1、Http网络协议简介2、Http网络协议的内容3、HTTP请求协议包组成4、HTTP响应协议包组成 一、常见的网络协议 常见的网络协议…

如何打包一个手机软件

目录 前言: 准备工具: 创建项目: 打包程序: 前言: 我们平时手机上使用的程序,或者电脑上使用的程序都可以由Web程序打包而来的,而打包不是一个.html文件也不是一个.js文件而是一个大型的文…

【AAOS车载系统+AOSP14系统攻城狮入门实战课】:正式上线了(二百零三)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

Blender怎么样启动默认移动和Cavity效果

在使用Blender的过程中,有一些特殊的技巧很重要。 比如默认地设置blender打开时,就是移动物体,这样怎么样设置的呢? 需要在界面里打开下面的菜单: 这样就找到默认设置的地方,把下面的移动勾选起来,这样点…

使用注意力机制的 LSTM 彻底改变时间序列预测

目录 一、说明二、LSTM 和注意力机制简介三、为什么要将 LSTM 与时间序列注意力相结合?四、模型架构训练与评估 五、验证六、计算指标七、结论 一、说明 在时间序列预测领域,对更准确、更高效的模型的追求始终存在。深度学习的应用为该领域的重大进步铺…

深度学习pytorch实战第P2周:CIFAR10彩色图片识别

>- **🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)** 零、引言(温故而知新&#xff…

C++算法 —— 前缀和

一、【模版】前缀和 1.链接 【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) 2.描述 3.思路 前缀和的思想其实就是一种简单的动态规划,以i位置记录从头位置到i位置的和,然后间接的求一段连续区间的数组和,时间复杂度是O(n&…

基于多模态单细胞数据构建共表达网络-MuSeGNN

本篇来自于MuSe-GNN: Learning Unified Gene Representation From Multimodal Biological Graph Data的补充材料。主要目的是从多模态数据中构建共表达网络。作者概述了使用CS-CORE,scTransform和SPARK-X进行预处理步骤和网络构建的算法细节。 目前存在大量用于图谱…

ESP32 引脚分配

请注意,以下引脚分配参考适用于流行的 30 引脚ESP32 devkit v1开发板。 仅输入引脚 GPIO34~39是GPIs–仅输入的管脚。这些引脚没有内部上拉或下拉电阻。它们不能用作输出,因此只能将这些管脚用作输入:GPIO 34、GPIO 35、GPIO 36、GPIO 39 S…

利用nginx-http-flv-module实现三种直播

目录 一、说明 二、目标 三、实现 四、直播地址 一、说明 此文在《流媒体服务器的搭建(支持hls)》《搭建nginx-http-flv-module直播系统》之后编写,很多详细内容需要参考它。 流媒体服务器的搭建(支持hls)

【解读Kubernetes架构】全面指南,带你掌握Kubernetes的设计原理与构成!

了解 Kubernetes 架构:综合指南 前言一、什么是 Kubernetes 架构?1.1、控制平面1.2、工作节点 二、Kubernetes 控制平面组件2.1、kube-api服务器2.2、etcd2.3、kube-scheduler2.4、Kube 控制器管理器2.5、云控制器管理器 (CCM) 三…

Web APIs简介 Dom

JS的组成 API API 是一些预先定义的函数,目的是提供应用程序与开发人员基于软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节 简单理解:API是给程序员提供的一种工具,以便能更轻松的实现…

车载电子电器架构 —— 工程EOL诊断

车载电子电器架构 —— 工程EOL诊断 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…

数据的统计描述

data.info() 提供了关于数据框的简要摘要,包括: 索引类型数据列的数量非空值的数量(针对每列)每列的数据类型(例如,int64, float64, object等)内存使用情况提供了哪些列可能包含缺失值&#xff…

flink on yarn

前言 Apache Flink,作为大数据处理领域的璀璨明星,以其独特的流处理和批处理一体化模型,成为众多企业和开发者的首选。它不仅能够在处理无界数据流时展现出卓越的实时性能,还能在有界数据批处理上达到高效稳定的效果。本文将简要…

Linux文件IO(4):目录操作和文件属性获取

目录 1. 前言 2. 函数介绍 2.1 访问目录 – opendir 2.2 访问目录 – readdir 2.3 访问目录 – closedir 2.4 修改文件访问权限 – chmod/fchmod 2.5 获取文件属性 – stat/lstat/fstat 2.5.1 文件属性 – struct stat 2.6 文件类型 – st_mode 3. 代码练习 3.1 要求 3.2 代…

day04-MQ

1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得到响应,但是你…

数组--有序数组的平方

LeetCode中的第977题: 思想:①返回每个新数组;②排序; (n个数,进行n-1趟比较。第j趟比较中要进行n-j次两两比较) (1)n个数,进行n-1趟比较: for(…

深度学习【向量化(array)】

为什么要向量化 在深度学习安全领域、深度学习练习中,你经常发现在训练大量数据时,深度学习算法表现才更加优越,所以你的代码运行的非常快至关重要,否则,你将要等待非常长的时间去得到结果。所以在深度学习领域向量化…

(源码+部署+讲解)基于Spring Boot和Vue的宠物领养系统的设计与实现

一、引言 本报告旨在详细描述基于Spring Boot后端框架和Vue前端框架的宠物领养系统的设计与实现过程。宠物领养系统旨在为宠物主人和领养者提供一个便捷的平台,实现宠物的信息发布、领养申请、信息管理等功能。通过该系统,宠物主人可以快速找到适合的领养…