Java并发系列之二:悲观锁机制

news2024/11/15 11:04:48

什么是锁

在并发环境下,会出现多个线程对同一个资源进行争抢的情况,假设A线程对资源正在进行修改,此时B线程此时又对资源进行了修改,这就可能会导致数据不一致的问题。为了解决这个问题,很多编程语言引入了锁机制,通过一种抽象的“锁”来对资源进行锁定,当一个线程持有“锁”的时候,其他线程必须等待“锁”,我认为这本质上就是在临界资源上对线程进行一种串行化。

Java语言中的锁机制是怎么设计的?在谈锁之前,我们需要简单了解一些Java虚拟机的内存结构。关于内存结构不是本文的重点,暂时不会影响到你的理解。我们可以来看这张图:

 

JVM运行时内存结构主要包含了五个部分:程序计数器(PC寄存器)、JVM栈、Native方法栈、 堆、方法区。

可以看到图中红色区域是各个线程私有的。这个区域中的数据,不会出现线程竞争的关系。而蓝色区域中的数据被所有线程共享,其中Java堆中存放的是大量对象,方法区中存放类信息、常量、静态变量等数据。当多个线程在竞争其中的一些数据时,可能会发生难以预料的异常情况。在程序开发中,锁的主要应用范围就是在数据共享区域。

了解了“锁”这种抽象的概念,那么在代码层面,它究竟是如何实现的?在Java中,主要采用了两种实现方式: 1. 基于Object的悲观锁。2. 基于CAS的乐观锁。本文主要讲解基于Object的悲观锁。

尝试用一句话概括:在Java中,每个Object,也就是每个对象都拥有一把锁,这把锁存放在对象头中,记录了当前对象被哪个线程占用。

对象、对象头

刚才提到了锁是存储在对象头中的,那么对象和对象头的结构分别是什么呢?

我们先来谈对象本身的结构,Java对象分为三个部分:

对象头

实例数据

对齐填充字节

TIPS:其中对齐填充字节是为了满足“Java对象大小是8字节的倍数”这一条件而设计的,为对象对齐填充了一些无用字节,大可不必理会。实例数据就是你在初始化对象时设定的属性和状态等内容。

对象头是我们这期要讲的重点之一,它存放了一些对象本身的运行时信息。对象头包含了两部分:

Mark Word

Class Pointer

相较于实例数据,对象头属于一些额外的存储开销,所以它被设计得极小(一般为232bit或264bit)来提升效率。Class Pointer是一个指针,指向当前对象类型所在方法区中的Class信息;Mark Word存储了很多当前对象的运行时状态信息,比如HashCode、 锁状态标志、指向锁记录的指针、偏向线程ID、锁标志位等等。

可以通过下面这张表对Mark Word有一个更直观的认识:

 

上面也提到了,对象头被设计得很小,Mark Word则主要体现了这一点,通过这张表我们可以看到,Mark Word只有32bit (或64bit) 并且它是非结构化的。这样,在不同的锁标识位下,不同字段可以重用不同的比特位,节省了空间。

我们从这张表中能看到,这把抽象的“锁”的信息就存储在对象头的Mark Word中。重点关注最后两位,这两位代表锁标志位,分别对应“无锁”、“偏向锁” 、“轻量级锁” 、“重量级锁”这四种状态。在Java中,启用对象锁的方式是使用、synchronizedi关键字,那么synchronized背后的原理是什么,上面列举的这些状态又都是什么意思呢?

synchronized

大家都知道在Java中,synchronized关键词可以用来同步线程,synchronized被编译后会生成monitorenter和monitorexit两个字节码指令,依赖这两个字节码指令来进行线程同步。

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

 

来看这张图,并模拟流程:

第一步:Entry Set中聚集了一些想要进入Monitor的线程,它们处于waiting状态。

第二步:假设某个名为A线程成功进入了Monitor,那么它就处于active状态。

第三步:此时A线程执行途中,遇到一个判断条件,需要它暂时让出执行权,那么它将进入wait set,状态也被标记为waiting。

第四步:这时entry set中的其他线程就有机会进入Monitor,假设一个线程B成功进入并且顺利完成,那么它可以通过notify的形式来唤醒wait set中的线程A,让线程A再次进入Monitor,执行完成后便退出。

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

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

目前为止,我们已经搞懂了什么是锁,什么是对象头,Mark Word中的字段,synchronized、

monitor的初步原理,四种锁状态的由来。

接下来,你一定会对synchronized是如何优化的?这四种状态是如何变化的产生好奇,那么我们就来仔细盘一盘“无锁”、“偏向锁”、“轻量级锁”、“重量级锁”这四种状态各是什么。

对象锁的四种状态

无锁:

无锁顾名思义就是没有对资源进行操作系统级别(Mutex Lock)的锁定。在这个基础上,我理解“无锁”其实有两种语义。

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

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

偏向锁:

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

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

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

轻量级锁:

当锁的状态还是偏向锁时,是通过Mark Word中的线程ID来找到占有这个锁的线程,那么当锁的状态升级到“轻量级锁”时,如何判断线程和锁之间的绑定关系呢?难道是通过不断的变更Mark Word中的线程ID的值?

非也,我们还是来看Mark Word这张表,这边已经不再使用线程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资源,于是出现一种叫做“适应性自旋”的优化,简单来说就是自旋的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的状态来决定。比如在同一个锁上,当前正在自旋等待的线程刚刚成功获得过锁,但是锁目前被其他线程持有,那么虚拟机就会认为下次自旋很有可能会再次成功,进而它将允许更长的自旋时间。这就是“适应性自旋”。

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

重量级锁:

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

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

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

相关文章

Stephen Wolfram:机器学习与神经网络训练

Machine Learning, and the Training of Neural Nets 机器学习与神经网络训练 We’ve been talking so far about neural nets that “already know” how to do particular tasks. But what makes neural nets so useful (presumably also in brains) is that not only can t…

【前端知识】React 基础巩固(四十六)——自定义Hook的应用

React 基础巩固(四十六)——自定义Hook的应用 一、自定义Hook的应用 自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上而言,它并不算React的特性。 实现组件创建/销毁时打印日志 import React, { memo, useEffect, useState } from "react…

#P0994. [NOIP2004普及组] 花生采摘

题目描述 鲁宾逊先生有一只宠物猴,名叫多多。这天,他们两个正沿着乡间小路散步,突然发现路边的告示牌上贴着一张小小的纸条:“欢迎免费品尝我种的花生!――熊字”。 鲁宾逊先生和多多都很开心,因为花生正…

java学习路程之篇五、进阶知识、常用API、Object类、Math类、System类、BigDecimal类、包装类

文章目录 1、介绍2、Object类3、Math类4、System类5、BigDecimal类6、包装类 1、介绍 2、Object类 3、Math类 4、System类 5、BigDecimal类 6、包装类

华为云低代码平台Astro Canvas 搭建汽车展示大屏——实验指导手册

实验背景 大屏应用Astro Canvas是华为云低代码平台Astro的子服务之一,是以数据可视化为核心,以屏幕轻松编排,多屏适配可视为基础,用户可通过图形化界面轻松搭建专业水准的数据可视化大屏。例如汽车展示大屏、监控大屏、项目开发大…

【Docker】Docker安装Consul

文章目录 1. 什么是Consul2. Docker安装启动Consul 点击跳转:Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套(质量有保证,内容详情) 1. 什么是Consul Consul是HashiCorp公司推出的开源软件,提…

JVM面试题--类加载器

什么是类加载器,类加载器有哪些 类加载子系统,当java源代码编译为class文件之后,由他将字节码装载到运行时数据区 BootStrap ClassLoader 启动类加载器或者叫做引导类加载器,是用c实现的,嵌套在jvm内部,…

站群站点日志优化

需求: 1,每个站点的日志需要记录到请求的域名 2,日志需要自动切割保存前三天的内容,防止日志无限增长 3,日志要有利于二次分析 实现: 1,nginx需要修改两个地方 nginx配置 时间 …

JavaScript(二)函数及对象

函数 函数体中return后面不能再添加任何代码&#xff0c;因为不会执行 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&quo…

【论文阅读24】Better Few-Shot Text Classification with Pre-trained Language Model

论文相关 论文标题&#xff1a;论文标题&#xff1a;Label prompt for multi-label text classification&#xff08;基于预训练模型对少样本进行文本分类&#xff09; 发表时间&#xff1a;2021 领域&#xff1a;多标签文本分类 发表期刊&#xff1a;ICANN&#xff08;顶级会…

PyCharm安装使用2023年教程,PyCharm与现流行所有编辑器对比。

与PyCharm类似的功能和特性的集成开发环境&#xff08;IDE&#xff09;和代码编辑器有以下几种&#xff1a; Visual Studio Code&#xff08;VS Code&#xff09;&#xff1a;由Microsoft开发&#xff0c;VS Code是一个高度可定制和可扩展的代码编辑器。它支持多种编程语言&am…

动手学深度学习(一)预备知识

目录 一、数据操作 1. N维数组样例 2. 访问元素 3. 基础函数 &#xff08;1&#xff09; 创建一个行向量 &#xff08;2&#xff09;通过张量的shape属性来访问张量的形状和元素总数 &#xff08;3&#xff09;reshape()函数 &#xff08;4&#xff09;创建全0、全1、…

机器学习笔记之优化算法(六)线搜索方法(步长角度;非精确搜索;Glodstein Condition)

机器学习笔记之优化算法——线搜索方法[步长角度&#xff0c;非精确搜索&#xff0c;Glodstein Condition] 引言回顾&#xff1a; Armijo Condition \text{Armijo Condition} Armijo Condition关于 Armijo Condition \text{Armijo Condition} Armijo Condition的弊端 Glodstein…

开源项目-知识库管理系统(中国软件杯项目)

简述 哈喽,大家好,今天带来一个开源项目-知识库管理系统,项目通过Spring MVC技术实现。通过readme了解到这是某位大神大三暑假(2016年)参加第五届中国软件杯项目的源码。由三人团队完成(Yu yufeng\Zhou changqin\Liu chenzhe) 此作品获得了本科组全国二等奖。项目本身用…

ROS处理kitti数据集

一、参考资料 kitti2bag代码仓库 二、KITTI数据集之tracking数据集 ROS1结合自动驾驶数据集Kitti开发教程(七)下载图像标注资料并读取显示 1. tracking数据集简介 tracking tracking任务分为三种类型&#xff0c;分别是Multi-Object Tracking&#xff08;多目标跟踪&…

2023牛客暑期多校训练营5-C Cheeeeen the Cute Cat

2023牛客暑期多校训练营5-C Cheeeeen the Cute Cat https://ac.nowcoder.com/acm/contest/57359/C 文章目录 2023牛客暑期多校训练营5-C Cheeeeen the Cute Cat题意解题思路兰道定理&#xff1a; 代码 题意 解题思路 可以将边 ( i , j n ) (i,jn) (i,jn)转变成 ( i , j ) (…

项目管理中的需求分析:实施策略与最佳实践

引言 在项目管理的过程中&#xff0c;需求分析起着至关重要的作用。理解和定义项目需求是项目成功的关键一步&#xff0c;它可以帮助我们确定项目的目标和范围&#xff0c;以及如何有效地达到这些目标。在本文中&#xff0c;我们将深入探讨需求分析的重要性&#xff0c;讨论如…

使用 AntV X6 + vue 实现单线流程图

使用 AntV X6 vue 实现单线流程图 X6 是 AntV 旗下的图编辑引擎&#xff0c;提供了一系列开箱即用的交互组件和简单易用的节点定制能力&#xff0c;方便我们快速搭建 DAG 图、ER 图、流程图等应用。 官方文档 安装 yarn add antv/x61.34.6Tips&#xff1a; 目前 X6 有 1.x…

css滚动条样式指南

css滚动条样式指南 滚动条是网页设计中经常被忽视的元素。虽然它看起来像是一个小细节&#xff0c;但它在网站导航中起着至关重要的作用。默认的滚动条可能看起来不合适&#xff0c;有损整体美观。本文将介绍如何使用 CSS 自定义滚动条。 在 Chrome、Edge 和 Safari 中设置滚…

微信小程序接入腾讯云天御验证码

腾讯云新一代行为验证码&#xff08;Captcha&#xff09;&#xff0c;基于十道安全防护策略&#xff0c;为网页、APP、小程序开发者打造立体、全面的人机验证。在保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时&#xff0c;提供更精细化的用户体验。 …