多线程常见的锁策略

news2024/9/20 18:37:05

目录

1.1 乐观锁 和 悲观锁

1.2 轻量级锁 和 重量级锁

1.3 自旋锁 和 挂起等待锁

1.4 互斥锁 和 读写锁

1.5 可重入锁 和 不可重入锁

1.6 公平锁 和 非公平锁

1.7 synchronized 锁的属性


一、锁策略

说到锁,Java 里面常用的锁有 synchronized ,锁的意义在于在多线程并发执行的时候保证线程安全,防止出现 BUG, 造成线程不安全的原因本质上是因为线程的调度是无序的,Java本身不负责调度,是由操作系统按照一定的规则来调度线程,CPU 并发执行,但是由于其他应用程序无法干预系统线程的调度,所以可以认为任务线程的调度是无序的。

锁策略就是设计一种解决线程安全问题的办法,在程序设计中根据业务需求需要去实现一个锁,基本上就是参考常见的锁策略。


1.1 乐观锁 和 悲观锁

顾名思义:

乐观锁,用来预测接下来的锁冲突概率不大,所谓锁冲突就是多线程针对同一对象加锁,同一时间内只有一个线程可以获取到锁并执行,其他线程阻塞等待锁释放。

悲观锁,用来预测接下来的锁冲突概率很大,这就涉及到程序需要不断的加锁、解锁,所以通常来讲悲观锁纯纯的打工仔,乐观锁工作的机会是比较少的,但是也不绝对。

悲观者总是喜欢将一件事往最坏的情况下预测,并且已经做好了最坏的打算,所以啊即使当那一刻真的来临,可能也会庆幸意料之中吧。

乐观者也许是珍惜当下的心态,每一天开开心心的就好了,即使遇到很不好的事情也能很快释然吧


1.2 轻量级锁 和 重量级锁

轻量级锁,即:加锁解锁的过程更快更高效。

常常使用在不同的线程交替的执行同步块中的代码(同步代码块指在代码块前加上 synchronized关键字的代码块),这种情况下,用重量级锁是没必要的。轻量级锁所适应的场景是线程交替执行同步块的场合,锁冲突较少的情况,如果出现多线程同时竞争同一把锁,锁冲突严重,那么就会导致轻量级锁膨胀为重量级锁。锁的强度对线程的状态的影响也是不同的。

重量级锁,即 :加锁解锁的过程更慢更低效

运用的场景那必然是多线程同时竞争同一对象锁,锁冲突严重,其他线程阻塞等待锁释放,当对象锁释放的时候,因锁竞争而阻塞等待的线程就会被唤醒,继续竞争锁,没有抢到的线程又会继续阻塞等待,如此以往,对资源还是有一定的消耗的。

线程阻塞等待即:该线程暂时不参与系统的调度,也就不会被 CPU 执行了。


1.3 自旋锁 和 挂起等待锁

轻量级锁基于自旋锁的一种实现,当多线程对同一对象锁进行竞争的时候,就会造成锁冲突,但是如果这种冲突并不严重的话,比如两个线程竞争,不是你就是我的,那没竞争到锁的另一个线程此时不会 “阻塞等待 ”,而是不断的循环尝试获取锁,当然这也会浪费CPU 的执行资源(也就是忙等状态),但是当锁释放的时候,自旋等待的线程能够第一时间获取到锁

重量级锁是基于挂起等待锁的一种实现,也就是锁冲突比较严重的状态,例如:有10 个线程竞争同一把对象锁,同一单位时间内只有一个线程能拿到锁并执行,那如果 10 个线程都自旋等待那就是得不偿失的,CPU 还得是并发的执行他们,让他们一直保持随时获取锁的状态,这消耗是得不偿失的,所以当锁冲突比较严重的时候直接让等待获取锁状态的线程进入阻塞等待,暂时不参与CPU的调度执行, 虽然会有唤醒线程,带来的开销,但是相较于一直并发执行这些线程,就大大降低了CPU 的开销。CPU 每秒钟可执行几十亿条指令。

基准速度:1 GHz 等于 一秒10 亿条指令。


 针对以上三种锁策略: synchronized 这把锁具有那些属性呢?

synchronized 既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁,轻量级锁基于自旋锁实现,重量级锁基于挂起等待锁实现。

synchronized 会根据当前锁竞争的程度,自适应的转化锁属性,如果锁冲突不激烈,是以乐观锁或乐观锁的状态运行,如果锁冲突激烈,以悲观锁或重量级锁的状态运行。


1.4 互斥锁 和 读写锁

互斥锁:

在多线程编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。  

多线程对同一对象(synchronized 修饰的代码块)进行操作就会发生锁竞争,竞争同一把对象锁,那么同一单位时间内只有一个线程能拿到锁并执行,此时其他线程就 “阻塞等待”,线程进入 synchronized 修饰的代码块就意味着获取到锁了,然后加锁,出代码块释放锁,解锁。实现一种互斥访问,不是你执行就是我执行,不管谁先进入,其他人都不能继续执行了。

读写锁:

顾名思义就是对读、写操作加锁。

在线程的环境下如果多个线程同时读数据不会有线程安全问题,如果多线程允许同时对一个变量的值进行修改,那完了,你改我也改(例如:对同一变量进行自增操作,两个线程同时读取的数据是一样的,但是如果写的时候线程1 先调度执行,修改完毕,写回内存,然后线程2 调度执行,修改完毕写回内存,那线程 2 修改后的值就会把线程 1 的值覆盖掉)很可能就对数据的有效性造成重大的影响。

所以读写锁的属性:

1. 读锁和读锁之间不会产生锁竞争

2. 写锁和写锁之间有锁竞争,同一单位时间内只有一个线程能拿到锁并执行写操作

3. 读锁和写锁之间也会有锁竞争,同一单位时间内只有一个线程能拿到锁并执行读操作,然后执行写操作

 Java 标准库中提供了两个专门的读写锁,读写锁是使用 ReentrantReadWriteLock 类来实现的

  • ReentrantReadWriteLock.ReadLock 表示读锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。
  • ReentrantReadWriteLock.WriteLock 表示写锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。

1.5 可重入锁 和 不可重入锁

如果一个线程执行 synchronized 修饰的代码块,意味着该线程获取到了对象锁,其他线程因无法获取对象锁而进入阻塞等待,等待锁释放,此时,如果该线程又执行到了 synchronized 修饰的代码块 ,是两个代码块嵌套的情况,而且使用的是同一个对象锁,概念是,一个对象只有一把锁,那么线程刚进入  synchronized 的代码块就获取了这把锁,此时又遇到需要获取该对象锁的情况,那么线程就有两种情况:

1. 骂骂咧咧的说谁把锁抢走了,气死我了,进入阻塞等待(死锁),因为谁拿到锁,就由谁释放锁,线程进入 synchronized 修饰的代码块获取锁,出 synchronized 修饰的代码块就会释放锁,这个时候别的线程就可以获取锁,现在这种情况是线程手里拿着锁,还没释放锁,又遇到需要获取该对象锁的情况,那他就会阻塞等待锁释放,产生了死锁问题,这就叫做 不可重入锁。

2. 由于线程手上有该对象锁,所以再遇到需要获取该对象锁的情况就直接验证一下是否是该锁的拥有者,如果是则允许进入,不会产生死锁问题,这就是是可重入锁。

Object locker = new Object(); // 使用第三方对象锁

synchronized(locker) {  //线程获取 locker 对象锁

   //线程再次获取 locker 对象锁,此时锁已经被线程获取了还未释放,就会有两种结果

   synchronized(locker) {

   }

}

概念有些晦涩难懂,接下来给大家举一些例子来讲述:

不知道大家有有没有在日常生活中偶尔遇到找某件东西怎么都找不到,找了一圈又一圈最后发现要找的东西就在自己手里,害。


在单线程的情况下,在某些特定的情况下可重入锁就非常好使,不会产生死锁问题。

但是如果是多个线程多把锁的情况下,即使是可重入锁,也会死锁~

 综上所述:可重入锁只有在单线程的状态下才好用,多线程就需要注意加锁顺序了。

 死锁的四个必要条件:

1. 互斥使用,一个线程拿到一把锁之后,另一个线程阻塞等待锁释放(张三上厕所,李四等待)

2. 不可抢占,一个线程拿到锁,只能自己主动释放,不能被其他线程强行占有。

3. 请求和保存状态,张三手里拿着对象锁,还一直尝试获取锁。

4. 循环等待,“家里的要是锁车里了,车钥匙锁家里了”,获取锁的逻辑是循环的,无法破局。


1.6 公平锁 和 非公平锁

因为同一单位时间内只有一个线程能获取到锁并执行,其他竞争同一对象锁的线程进入阻塞等待的状态,此时如果又加入了几个线程来竞争对象锁,当对象锁释放的时候,这些线程之间获取到对象锁的概率是均等的,没有先来后到之理,这叫做 ——非公平锁。

反之,如果锁竞争的线程遵守先来后到,当对象锁释放的时候,最先尝试获取对象锁失败进入等待状态的线程最先获取,那么这就叫做——公平锁。

这些组锁概念还是很容易理解的,画个图理解一下:

​编辑

 Java 中提供的 synchronized 属于非公平锁,如果想要实现公平锁,可以在 synchronized 的基础上使用队列来记录这些线程任务,put  执行。


1.7 synchronized 锁的属性

synchronized 锁的特点:

1. 既是乐观锁,也是悲观锁

2. 既是轻量级锁,也是重量级锁

3. 轻量级锁是基于自旋锁实现,重量级锁是基于挂起等待锁实现

4. 不是读写锁,是互斥锁

5. 是可重入锁

6. 是非公平锁

以上锁属性上文都有解析。


人终将被年少不可得之物困其一生,也终会因一事一景解开一生困惑。

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

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

相关文章

安全防御 --- 态势感知、VPN

一、态势感知 1、概念 态势感知(SA --- Situational Awareness)是对一定时间和空间内的环境元素进行感知,并对这个元素的含义进行理解,最终预测这些元素在未来的发展状态。 作用: 态势感知能够检测出超过20大类的云上…

使用nvm(node.js version management)安装管理nodejs

鉴于目前网络上关于NVM安装NodeJS操作步骤的文章,大家都在互相借鉴,很少看到原创文章,很多操作步骤已经冗余和过时。因此,本人根据实际的前端项目开发经验,在此重新梳理了一遍目前最新的NVMNodeJS的安装步骤&#xff0…

微信小程序原生开发功能合集十二:编辑界面的实现

本章实现编辑界面的实现处理,包括各编辑组件的使用及添加数据保存数据流程的实现处理。   另外还提供小程序开发基础知识讲解课程,包括小程序开发基础知识、组件封装、常用接口组件使用及常用功能实现等内容,具体如下:    1. CSDN课程: https://edu.csdn.net/course/…

BOSHIDA 模块电源高低温试验箱测试原理

BOSHIDA 模块电源高低温试验箱测试原理 电源模块是可以直接贴装在印刷电路板上的电源供应器,其特点是可为专用集成电路(ASIC)、数字信号处理器 (DSP)、微处理器、存储器、现场可编程门阵列 (FPGA&#xf…

OpenShift:关于OpenShift(OKD)通过命令行的方式部署镜像以及S2I流程Demo

写在前面 因为参加考试,会陆续分享一些 OpenShift 的笔记博文内容为安装完 OpenShift, 利用 OpenShift 引擎部署一个镜像应用和一个 S2I 流程部署应用 Demo学习环境为 openshift v3 的版本,有些旧这里如果专门学习 openshift ,建议学习 v4 版…

【Idea】人工智能编程他来了,Idea集成一款和ChatGPT一样智能的编码辅助神器

文章目录 简介官方介绍功能介绍 注册使用使用方法功能说明 结尾 简介 Bito是一款建立在OpenAI和ChatGPT之上的开发辅助神器!他可以帮我们生成代码、语法提示、生成测试用例、解释代码含义、生成注释、优化代码、检测代码安全性以及学习理论知识等!我们可…

这可能是最全面的MySQL面试八股文了

什么是MySQL MySQL是一个关系型数据库,它采用表的形式来存储数据。你可以理解成是Excel表格,既然是表的形式存储数据,就有表结构(行和列)。行代表每一行数据,列代表该行中的每个值。列上的值是有数据类型的…

Ubuntu更新软件下载更新与移除

目录 一、更新软件源 二、下载与安装软件 三、如何移除软件 四、Ubuntu商店下载软件 一、更新软件源 更新Ubuntu软件源的操作步骤,更新软件源的目的就是,将在Ubuntu官网的软件源更改到本地,也就是国内的软件源,这样的话下载安…

北邮22信通:二叉树各种遍历所有常见算法汇总

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 获取更多文章 请访问专栏~ 北邮22信通_青山如墨雨如画的博客-CSDN博客 目录 1.二叉树的前序遍历 1.1递归算法 1.2非递归算法 1.2.1模板类实现栈 1.…

负载均衡的综合部署练习(LVS-DR模式+Nginx七层反向代理+Tomcat多实例)

1.实验设计 实验所满足的需求 满足某公司,想搭建一套高可用的负载均衡DR模式的集群,同时该集群收到用户访问请求时能够自主判断用户发送的请求是动态资源还是静态,依次划分进行动静分离:Nginx处理静态资源,Tomcat处理…

图的数据结构,系统学习图的基本概念、定义和建立,学会邻接矩阵、邻接表以及实现六度空间案例,遍历图的方式——广度、深度访问

1.图的定义和术语 图:G (V,E) Graph (Vertex, Edge) V:顶点(数据元素)的有穷非空集合; E:边的有穷集合。 有向图:每条边都是有方向的 无向图:每条边都是无方向的 完全图&#…

用 ChatGPT 进行阅读理解题目的问答

阅读理解出题 阅读理解题是语言学习过程中一种重要的练习方式。无论语文还是英语考试中,阅读理解题都占有相当大的分值。ChatGPT 作为一种大语言模型,在处理自然语言理解任务中具有很大的优势。广大教师和学生家长们,都可以尝试用 ChatGPT 进…

借灰姑娘的手,讲述js混淆加密的美丽

这个故事的主角是灰姑娘,她有一个重要的秘密,需要将其保护起来。但是,她发现她的网站上的 JavaScript 代码很容易被其他人阅读和修改,为了保护这个秘密,她需要采用一些混淆和加密技术。 以下是她使用的一些技术&#…

数据结构与算法学习:二叉树的后序遍历的递归与非递归实现,以及非递归实现中的流程控制的说明。

需求二叉树: 采用二叉树后序遍历非递归算法。设置一个指针p初始指向树根,p先入栈,而后使得p指向它的左孩子p->firstchild,重复操作,使得每个左孩子都依次入栈,同时初始化一个Treenode*类型的指针pre&…

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT,来源于论文Improving Language Understanding by Generative Pre-Training(翻译过来就是:使用通用的预训练来提升语言的理解能力)。GPT这个名字其实并没有在论文中提到过,后人将论文名最后…

【软件测试】知识图

文章目录 第1章 软件测试概述1.1 软件、软件危机和软件工程1.1.1 基本概念1.1.2 软件工程的目标及其一般开发过程1.1.3 软件过程模型 1.2 软件缺陷与软件故障1.2.1 基本概念1.2.2 典型案例 1.3 软件测试的概念1.3.1 软件测试的定义1.3.2 软件测试的目的:保证软件产品…

备忘录设计模式解读

目录 问题引进 游戏角色状态恢复问题 传统方案解决游戏角色恢复 传统的方式的问题分析 备忘录模式基本介绍 基本介绍 备忘录模式的原理类图 对原理类图的说明 游戏角色恢复状态实例 应用实例要求 思路分析和图解(类图) 代码实战 备忘录模式的注意事项和细节 问题引…

了解网络攻击:类型、策略和技术

近年来,网络攻击变得越来越普遍,个人和企业都成为各种网络威胁的受害者。了解不同类型的网络攻击,以及网络罪犯使用的策略和技术,对于保护您的个人和企业数据免受这些威胁至关重要。 有几种不同类型的网络攻击,每种都…

Linux 查看进程和线程CPU和内存占用情况

文章目录 Linux 查看进程有哪些线程Linux 查看程序内存占用情况 top和free等命令Linux 查看进程、线程数量 Linux 查看进程有哪些线程 linux 下查看进程内的线程有哪些 首先通过进程名称,假设为SensorDev 找到pid号。 ps -p {pid} -T 可以得到该进程里面运行的各…

Mapbox多边形光效晕影特效的实现

相信很多大屏需要展示行政区的发光效果,像下图这样的: 这相比普通的多边形样式,边界有了渐变发光的效果,那么这篇章交给大家如何实现这样一个效果,让你的行政区,地块之类的多边形要素展示成发光的效果。 我们不依赖底层的webgl技术,也不用涉及到什么着色器的概念,我…