synchronized同步锁机制

news/2024/12/2 22:01:41/文章来源:https://www.cnblogs.com/leizia/p/18582849

目录
  • synchronized 的使用
  • Java的对象头和 Monitor
    • 对象头
    • 实例数据
    • 对齐填充
  • synchronized 原理
    • synchronized修饰代码块示例
  • 对象锁的四种状态
    • 无锁
    • 偏向锁
    • 轻量级锁
    • 重量级锁


synchronized 的使用

  • 如果修饰的是具体对象:锁的是对象
  • 如果修饰的是成员方法:那锁的就是 this
  • 如果修饰的是静态方法:锁的就是这个对象.class

Java的对象头和 Monitor

理解 synchronized 原理之前,我们需要补充一下 java 对象的知识。

对象在内存中的布局分为三块区域:

对象头

实例数据

对齐填充

对象头

Hot Spot 虚拟机对象的对象头部分包括两类信息。

第一类是用于存储对象自身的运行时数据,如哈希码( Hash Code)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它为“Mark Word”。

从上图中能看到,把抽象的"锁"的信息存储在对象头的MarkWord中,重点关注最后两位,这两位代表锁标志位,分别对应:无锁、偏向锁、轻量级锁、重量级锁 这四种状态。在Java中,启动对象锁的方式是使用synchronized关键字。

实例数据

实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。

对齐填充

并不是必然存在的,由于 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

synchronized 原理

在java中,synchronized关键字可以用来同步 线程,synchronized被编译后会生成monitorentermonitorexit两个字节码指令,依赖这两个字节码指令来进行线程同步。

synchronized修饰代码块示例

public class Tues {public int i;public void syncTask(){synchronized (this){i++;}}
}

反编译 class 文件:

Java虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义。(monitorenter 和 monitorexit 两条指令是 C 语言的实现)正确实现 synchronized 关键字需要 Javac 编译器与 Java 虚拟机两者共同协作支持。Monitor的实现基本都是 C++ 代码,通过JNI(java native interface)的操作,直接和cpu的交互编程。

Monitor常常被翻译成监视器或管程。关于Monitor,简单来说可以把它想象成一个只能容纳一名客人房间,而把想要获取对象锁的线程想象成想要进入这个房间的客人。一个线程进入了Monitor,那么其他线程只能等待,只有当这个线程退出,其他线程才有机会进入。

这就是synchronized关键字所实现的同步机制,但是synchronized可能存在性能问题,因为Monitor的下层是依赖于操作系统的Mutex Lock来实现的。Java线程事实上是对操作系统线程的映射,所以每当挂起或唤醒一个线程都要用户态切换到操作系统的内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这个操作是比较重量级的。在某些情况下,甚至切换时间本身就会超出线程执行任务的时间,这样的话,使用synchronized将会对程序的性能产生影响。

但是从Java6开始,synchronized进行了优化,引入了 偏向锁、轻量级锁的概念。因此对象锁总共有四种状态,从低到高分别是:无锁、偏向锁、轻量级锁、重量级锁,这就分别对应了Mark Word中锁标记位的四种状态。

目前为止,我们已经搞懂了什么是锁,什么是对象头,Mark Word中的字段,synchronized、monitor的初步原理,四种锁状态的由来。

对象锁的四种状态

无锁->偏向锁->轻量级锁->(自旋)->重量级锁。按照这个顺序,锁的重量依次增加。

无锁

无锁顾名思义就是没有对资源进行操作系统级别(Mutex Lock)的锁定。我理解"无锁"其实有两种语义。

第一种比较简单,某种资源不会出现在多线程环境下,或者说即使出现在多线程环境下也不会出现线程竞争的情况,那么确实无需对这个资源进行任何同步保护,直接让他给各个线程随意调用就可以。

另一种情况,资源会被竞争,但是不使用操作系统同步原语对共享资源进行锁定,而是通过一些其他机制来控制同步。比如 CAS,通过诸如这种函数级别的锁,我们可以进行"无锁"编程。顺便一提的是,依赖操作系统Mutex Lock是导致性能低下的原因,所以在大部分情况下,无锁的效率更高,但这并非意味着无锁能够全面代替有锁。

偏向锁

假如一个对象被加锁了,但在实际运行时,只有一条线程会获取这个对象锁,那么我们最理想的方式,是不要通过系统状态切换,只在用户态把这件事做掉,我们设想的是,最好对象锁能够认识这个线程,只要是这个线程过来,那么对象就直接把锁交出去。我们可以认为这个对象锁偏爱这个线程,所以被称为"偏向锁"。

意思是这个锁会偏向于第一个获得它的线程,当这个线程再次请求锁的时候不需要进行任何同步操作,从而提高性能。那么处于偏向锁模式的时候,对象头的Mark Word 的结构会变为偏向锁结构。

在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁的代价而引入偏向锁。那么显然,一旦另一个线程尝试获得这个锁,那么偏向模式就会结束。另一方面,如果程序的大多数锁都是多个线程访问,那么偏向锁就是多余的。

那么偏向锁是怎么实现的呢?其实很简单,在Mark Word中,当锁标志位是'01',那么判断倒数第三个bit是否为1,如果是1,代表当前对象的锁状态为偏向锁,于是再去读Mark Word的前23个bit,这23个bit就是线程ID,通过线程ID来确认想要获得对象锁的线程是不是"被偏爱的线程"。

假如情况发生了变化,对象发现目前不只有一个线程,而是有多个线程正在竞争锁,那么偏向锁将会升级为轻量级锁。

轻量级锁

当锁状态还是偏向锁时,是通过Mark Word中的线程ID来找到占有这个锁的线程,当偏向锁的条件不满足,亦即的确有多线程并发争抢同一锁对象时,但并发数不大时,优先使用轻量级锁。一般只有两个线程争抢锁标记时,优先使用轻量级锁。那么当锁的状态升级到"轻量级锁"时,已经不再使用线程ID这个字段,而是将前30个bit变为了指向虚拟机栈中锁记录的指针。

当一个线程想要获得某个对象的锁时,假如看到锁标志位为'00',那么就知道它是轻量级锁,这时,线程会在自己的虚拟机栈中开辟一块被称为Lock Record的空间。Lock Record中存放什么呢?存放的是对象头Mark Word的副本以及Owner指针。线程通过CAS去尝试获取锁,一旦获得,那么将会复制该对象的Mark Word到虚拟机栈的Lock Record中,并且将Lock Record中Owner指针指向该对象锁。另一方面,对象的Mark Word中的前30bit将生成一个指针,指向持有该对象锁的线程虚拟机栈中的Lock Record。这样一来就实现了线程和对象锁的绑定,它们因此互相知道对方的存在。

这时,这个对象被锁定了,获取了这个对象锁的线程就可以去执行一些任务。那么你肯定要问,这时候万一有其他线程也想要获取这个对象,怎么办呢?此时其他线程将会自旋。什么叫"自旋"?可以理解为一种轮询,其他想要获取对象锁的线程自己不断在循环尝试去看一下锁有没有被释放,如果被释放了,那么就获取,如果没有释放就进行下一轮循环,这种方式区别于被操作系统挂起阻塞,因为如果对象锁很快就被释放的话,自旋去获得锁完全在用户空间解决,不需要进行系统中断和现场恢复,所以它的效率更高。

自旋相当于CPU在空转,如果长时间自旋,将会浪费CPU资源,于是出现一种叫做"适应性自旋"的优化,简单来说就是自旋的时间不再固定了,而是由上一次在同一个锁上的自旋时间以及锁的状态来决定。比如在同一个锁上,当前正在自旋等待的线程刚刚成功获得过锁,但是锁目前被其他线程持有,那么虚拟机就会认为下次自旋很有可能再次成功,进而它将允许更长的自旋时间,这就是"适应性自旋"。

假如对象锁被一个线程持有着,此时也有一个线程正在自旋等待,如果同时又有多个线程想要获取这个对象锁。也就是说,一旦自旋等待的线程超过一个,那么轻量级锁将会升级为"重量级锁"。

重量级锁

如果对象锁状态被标记为重量级锁,需要通过Monitor来对线程进行控制,此时将会使用同步原语来锁定资源,对线程的控制也最为严格。

注意:Java中的各种锁对程序员来说是透明的: 在创建锁时,JVM 先创建最轻的锁,若不满足条件则将锁逐次升级。这四种锁之间只能升级,不能降级

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

员工出入更衣室穿戴规范识别检测系统

员工出入更衣室穿戴规范识别检测系统能够通过安装在更衣室入口的摄像机,员工出入更衣室穿戴规范识别检测系统实时检测员工的穿戴情况。系统的工作流程如下:当员工进入更衣室时,摄像机捕捉到图像,算法迅速识别图像中的人员,并检测他们是否穿戴了规定的防护服、护目镜、口罩…

Lock接口

目录Lock接口Lock接口概述API方法锁获取与中断Synchronized和Lock的区别 Lock接口大佬地址: AQS(AbstractQueuedSynchronizer)源码深度解析(2)—Lock接口以及自定义锁的实现Lock接口概述 Lock接口同样自于JDK1.5,它被描述成JUC中的锁的超级接口,所有的JUC中的锁都会实现Lock…

作文的深度解析

目录题目一:There is a growing awareness of the importance of digital literacy and skills in todays world题目二:Nowadays more and more college students have come to realize social practice and academic learning are equally important. 题目一:There is a gr…

终极Redis

Redis是世界上最流行的数据存储之一,功能丰富。这里有8个简单的步骤可以帮助你理解Redis的基本原理。1、什么是Redis?Redis(远程字典服务器)是一个多模式数据库,提供亚毫秒级的延迟。Redis背后的核心思想是缓存也可以作为一个完整的数据库。2、Redis采用Airbnb、Uber、Sla…

AI电动车头盔识别系统解决方案

AI电动车头盔识别系统解决方案通过在关键路段及社区入口等位置安装高清摄像头,AI电动车头盔识别系统解决方案结合深度学习算法对电动车骑行者进行实时监测,确保骑行者的安全。识别到未佩戴头盔的骑行者时,AI电动车头盔识别系统解决方案将立即联动附近的智能广播系统播放预先…

H5-17 选择器

CSS语法 规则由两个主要的部分构成:选择器,以及一条或多条声明(样式) 1、全局选择器可以与任何元素匹配,优先级最低,一般做样式初始化*{margin:0;padding:0;} 2、元素选择器HTML 文档中的元素,p、b、div、a、img、body等标签选择器,选择的是页面上所有这种类型的标签…

canal的安装搭建

canal介绍主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。 这里我们可以简单地把canal理解为一个用来同步增量数据的一个工具。canal能做什么数据库镜像 数据库实时备份 索引构建和实时维护 业务cache(缓存)刷新 带业务逻辑的增量数据处理首先开启mysql bi…

ctfshow 信息搜集web入门思路

做ctfshow的思路(web2)js前台拦截 无法使用f12打开代码 可以使用 ctrl+u 或者在网站前面加上view-source: 或者使用bp进行抓包(web4)robots.txt 中可能包含着信息(web5)phps源码泄露index.phps(web6)源码泄露 www.zip 泄露(web7)/.git/ 源文件泄露(web8)index.p…

分区函数partition by的基本用法【转载】

本章将和大家分享分区函数partition by的基本用法。本章将和大家分享分区函数partition by的基本用法(此处以MySQL为例)。废话不多说,下面我们直接进入主题。 一、建表语句-- 创建商品表 CREATE TABLE commodity (id int NOT NULL PRIMARY KEY COMMENT 主键,position VARCHA…

JavaSwing外观美化

系统可选风格 windows风格: com.sun.java.swing.plaf.windows.WindowsLookAndFeelwindows Classic风格: com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel Metal风格 ( Swing默认) :avax.swing.plaf.metal.MetalLookAndFeel Motif风格 : com.sun.java.swing.plaf…

网络架构

二层交换机电脑通过网线与交换机连接,形成局域网 发送信息时,电脑将信息发给交换机广播,传给其他电脑。 路由器为了避免广播风暴,引出路由器,通过路由器连接每个交换机 路由器上会配置网关,从而识别每个局域网,将消息发到特定的局域网中

MySQL底层概述—9.ACID与事务

大纲 1.ACID之原子性 2.ACID之持久性 3.ACID之隔离性 4.ACID之一致性 5.ACID的关系 6.事务控制演进之排队 7.事务控制演进之排它锁 8.事务控制演进之读写锁 9.事务控制演进之MVCC 10.事务隔离级别之隔离级别的类型 11.事务隔离级别之和锁的关系 12.事务隔离级别之隔离级别的控制…