【Java EE初阶九】多线程进阶一(锁策略)

news2024/10/1 7:35:00

前言

        锁的策略:加锁过程中,即处理冲突的过程中,需要涉及到的一些不同的处理方式(此处锁策略并不是java独有的),本篇内容主要是讲解一下关于锁的相关知识点;

1. 关于锁的分组

1.1 第一组:乐观锁和悲观锁

------>这是两种不同的锁的实现方式

        乐观锁:在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候不会做太多的工作。加锁过程中做的事情比较少,加锁的速度可能更快,但是更容易引入一些其他的问题(即可能会消耗更多的cpu资源)

        悲观锁:在加锁之前,预估当前出现锁冲突的概率比较大,因此在进行加锁的时候会做更多的工作,因为做的事情更多,所以加锁的速度回更慢,但是整个过程中不容易出现其他的问题。

1.2 第二组:轻量级锁和重量级锁

        轻量级锁,加锁的开销更小,加锁的速度更快--->轻量级锁,一般都是乐观锁

        重量级锁,加锁的开销更大,加锁的速度更慢--->轻量级锁,一般都是悲观锁

        轻量重量是加锁之后对结果的评价,悲观乐观,是加锁之前,对未来的事情进行的预估,根据整体来说,这两种角度,描述的都是同一个事情;

1.3 第三组:自旋锁和挂起等待锁

        自旋锁:是轻量级锁的一种典型实现;加锁的时候,如果加锁失败,不会就此阻塞放弃cpu,而是相当于添加一个while循环,不停的进行锁竞争,这次锁竞争失败了就再次进入循环竞争锁,直到加锁成功,才退出循环。

        这种反复快速的执行,就称为 “自旋”,故此自旋锁也是乐观锁,使用自旋锁的前提是预期锁冲突概率不大,只要其他线程释放了锁,它就能第一时间加锁成功;但如果有很多线程要加锁,就必要使用自旋锁了,因为会浪费cpu资源。

        等待挂起锁:是重量级锁的一种典型体现,也是悲观锁;加锁的时候,如果加锁失败,就会等待一段时间,这段时间它不会去进行加锁操作和别的线程锁竞争,故此就能把一些cpu资源让出来,被让出来的这些cpu资源可以干一些其他事情,等一段时间过后,会再次尝试加锁,如果失败还是重复以上工作,直到最后成功拿到锁;

        当等待挂起的时候,会有内核调度器介入,所以就需要完成的操作就多了,从而要获取锁的时间花费也更多一些。

Q:java中的synchronized算以上锁的哪种情况?

A:synchronized具有自适应能力,且synchronized在某些情况下是乐观锁,轻量级锁,自旋锁;但是在某些情况下时悲观锁,重量级锁,等待挂起锁,当然系统内部会自动的评估当前锁冲突的激烈程度;

        如果当前锁冲突的激烈程度不大,就处于乐观锁\轻量级锁\自旋锁;

        如果当前锁冲突的激烈程度很大,就处悲观锁锁\重量级锁\等待挂起锁;

1.4 第四组: 普通互斥锁和读写锁

        普通互斥锁:类似synchronized这种,操作涉及到加锁、解锁

        读写锁:这里加锁的情况分为两种:加读锁,加写锁

        读锁和读锁之间,不会出现锁冲突(不会阻塞);写锁和写锁之间,会涉出现锁冲突(会阻塞);读锁和写锁之间,会出现锁冲突(会阻塞);当一个线程加读锁时,另一个线程只能读,不能写;当一个线程加写锁时,另一个线程不能写,也不能读

Q:为啥要引入读写锁?

A:     如果两个线程读,本身就是线程安全的,不需要进行互斥;

        如果使用synchronized这种方式加锁,两个线程读与会产生互斥(阻塞),对于性能有一定的损耗,完全给读操作不加锁,就怕连个线程一个是读线程,一个是写线程,这样更加会涉及锁冲突;如上所述,读写锁能很好地解决上述的问题

         java标准库中,也提供了专门的类,来实现读写锁(读写锁本质上还是系统提供的读写锁,系统提供api,jvm中将api进行封装给我们使用),注意synchornized不是读写锁;

1.5 第五组:公平锁和非公平锁

        此处的公平即遵循先来后到的规则才是公平;

        公平锁:如果线程和线程之间,锁竞争的时间大小不一样,按照锁竞争时间久的线程先拿到锁,有先后顺序(先来后到的意思)

       非公平锁:线程和线程之间没有拿锁顺序,随机调度,各凭各的本事拿锁。

        这里使用公平锁,就能很好的解决线程饿死的这一问题。而要想实现公平锁,就需要引入额外的数据结构(引入队列,记录每个线程的先后顺序),才能实现公平锁。

1.6 第六组:可重入锁和不可重入锁

        可重入锁如synchronized,加锁一段代码,锁里面可以再进行一次加锁,锁里面可以嵌套多个锁,里面是用计数器这种方式对加锁的数量进行计数,并判断是否解锁,是可重入锁。

        不可重入锁系统自带的锁,不能连续加锁两次。

1.7 synchronized和Linux基于特定锁的简单比较

        下面来说明一下synchronized和系统自带的锁的具体锁;
synchronized:乐观锁 / 悲观锁自适应

                        轻量级锁 / 重量级锁自适应

                        自旋锁 / 挂起等待所自适应

                        不是读写锁

                        非公平锁

                        可重入锁

linux提供的mutex(系统自带的锁):          

                        悲观锁

                        重量级锁

                        挂起等待所

                        不是读写锁

                        非公平锁

                        不可重入锁

 

2. 细说synchronized的自适应

        Synchronized的加锁过程,自适应如下所述:

        当线程执行到synchronized的时候,如果这个对象处于未加锁的状态,就会经历一下的过程:

2.1 锁升级

        1、偏向锁阶段

        核心思想:“懒汉模式”,就是能不加锁,就不加锁;所谓的偏向锁,并不是真的加锁了,只是做了个非常轻量的标记。

        一旦有线程来竞争这个锁,持有偏向锁的线程能第一时间拿到这个锁,如果没有其他线程竞争锁,下次还是拿到锁的线程大概率还是持有偏向锁的线程。

        总的来说,当有锁竞争的情况下,偏向锁没有提高效率,在没有锁竞争的情况下,偏向锁就能大幅度的提高效率了。

        这个标记是锁里面的一个属性,每个锁都有自己的标记,当锁对象首次加锁是,没有涉及到锁竞争,这个阶段就是偏向锁阶段,一旦涉及到锁竞争,就会升级成轻量级锁阶段。

        2、轻量级锁阶段

        此处的轻量级锁就是通过自旋的方式实现的。假设有锁竞争,但不多的时候,就会处于轻量级锁阶段,它的优势:当其他线程释放锁了,处于轻量级锁阶段的线程能第一时间拿到锁;劣势:比较消耗cpu资源,因为是自旋的方式实现的,会有个循环一直尝试拿锁。

        当线程多了,轻量级锁就不合适了,每个线程都循环尝试拿锁,但如果已经有线程拿到锁了,其他线程要阻塞等待,但等待的这过程是会有循环不断的尝试拿锁,这里消耗的cpu资源就很多了。这时,就会从轻量级锁阶段升级成重量级锁阶段。

        3、重量级锁阶段
        此处的重量级锁是用挂起等待的方式实现的,当有很多线程同时去竞争这个锁时,有个线程拿到锁了,其他线程没拿到,就会阻塞等待,等待一段时间再去尝试拿锁,不成功再阻塞等待一段时间,循环以上步骤,如此就会让出cpu的资源,可以利用这些cpu资源干一些其他的事。

        注意:当前版本此处只能升级,不能降级。

2.2 锁消除 

        锁消除是synchronized的内部优化;

        有时候,有些代码可以一眼看上去就不用加锁,但是代码加锁了,这时候,编译器就会把这个锁给消除掉,毕竟加锁操作也是要消耗一些硬件资源的。

        注意:锁消除和偏向锁的区别

        锁消除:针对能够一眼就看出不涉及到线程安全问题的代码,编译器能够把锁给消除掉。

        偏向锁:是已经运行代码了,才知道没有锁竞争。

2.3 锁粗化

        通常情况下,我们更偏好于让锁更细一些,这样更有利于并发编程的时候解决线程安全问题;但有时候,让锁更粗写能提高效率,会希望锁粗点。

        锁粗化:把多个细粒度的锁合并成一个粗粒度的锁。如下图所示:

        一段代码中,频繁的加锁解锁,肯定会消耗更多的硬件资源,但是如果能把一段代码的多个加锁、解锁操作,优化成只有一次加锁、解锁,这样也能提高效率。

ps:关于多线程中锁策略的内容就到这里了,如果对你有所帮助的话,就请一键三连哦!!!

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

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

相关文章

【Docker】容器的相关命令

上一篇:创建,查看,进入容器 https://blog.csdn.net/m0_67930426/article/details/135430093?spm1001.2014.3001.5502 目录 1. 关闭容器 2.启动容器 3.删除容器 4.查看容器的信息 查看容器 1. 关闭容器 从图上来看,容器 aa…

【Windows】之微软输入法配置小鹤双拼

前言 Windows 自带的输入法微软输入法本身就是个最简洁、最方便的输入法,不需要去安装多余的第三方输入法软件。同时,微软中文拼音输入法支持双拼输入法,但微软自带的双拼输入法不包含小鹤双拼方案的。所以,在这里将会讲解如何配置…

【Redis】非关系型数据库之Redis的介绍及安装配置

目录 前言 一、关系型数据库与非关系型数据库 1.1关系型数据库 1.2非关系型数据库 1.3两者的区别 1.4非关系型数据库产生的背景 1.5总结 二、Redis介绍 2.1Redis是什么 2.2Redis的优点 2.3Redis的使用场景 2.4那些数据适合放在缓存中 2.5Redis为什么那么快&#xf…

Linux内核组成

Linux内核的组成 /boot/vmlinuz-4.18.0-80.el8.x86_64 :启动时用到的内核 /lib/modules/4.18.0-80.el8.x86_64 :内核模块 /boot/initramfs-4.18.0-80.el8.x86_64.img :启动时提供必要的内核模块 kernel-core安装包里面包含了Linux内核启动…

如何科学地防范冬季流感

如何科学地防范冬季流感 加强对呼吸系统传染病预防的观念 在乘坐地铁、公交、火车、飞机等公共交通工具时,应科学佩戴口罩。要经常洗手,定期通风,咳嗽或打喷嚏时要用手捂住口鼻,不要随地吐痰。 羊大师建议积极接种含有XBB变异株…

用判断对齐大语言模型

1、写作动机: 目前的从反馈中学习方法仅仅使用判断来促使LLMs产生更好的响应,然后将其作为新的示范用于监督训练。这种对判断的间接利用受到无法从错误中学习的限制,这是从反馈中学习的核心精神,并受到LLMs的改进能力的制约。 2…

Java运算符简单介绍

文章目录 1. 算术运算符2. 赋值运算符3. 比较(关系)运算符4. 逻辑运算符5. 位运算符6. 条件运算符(三元运算符)7. 运算符优先级 1. 算术运算符 :加法 int a 5; int b 3; int sum a b; // 结果为8-:减…

Docker安装WebRTC下TURN服务

详细实现方式以及代码下载请前往 https://www.passerma.com/article/90 实现效果 一、手动构建镜像 1.新建Dockerfile文件 文件用于编译镜像 以alpine为基础镜像 添加coturn需要的依赖库 获取coturn并进行编译 通过start.sh启动turnserver服务 Dockerfile FROM alpineRUN ap…

安卓Android Studioy读写NXP ICODE2 15693标签源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-21818769070.11.4391789eCLwm3t&id615391857885 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xm…

零售EDI:Metro EDI项目案例

麦德龙Metro 总部位于杜塞尔多夫&#xff0c;在全球范围内经营批发和零售业务。在2018/2019 财年&#xff0c;麦德龙Metro 的全球销售额约为 270 亿欧元。从2016年开始&#xff0c;麦德龙Metro就开始对其当时约230家门店和20,000多家分销合作伙伴进行数字化整合&#xff0c;借助…

针对人工智能的攻击并发布策略和建议

人工智能系统已经渗透到现代社会的各个领域。从自动驾驶到疾病诊断以及作为在线聊天机器人与客户互动。 为了学习如何执行这些任务&#xff0c;聊天机器人需要接受大量数据的训练。然而&#xff0c;主要问题之一是这些数据可能不可靠。攻击者有很多机会破坏它们。这都是在AI系…

第121场双周赛题解:揭秘算法竞赛中的数位挑战与解题策略

需要多掌握解题套路 比赛地址 100157. 大于等于顺序前缀和的最小缺失整数 class Solution:def missingInteger(self, nums: List[int]) -> int:# Step 1: Find the longest consecutive prefixi 0 for i in range(1, len(nums)):if nums[i] ! nums[i - 1] 1:breakelse:…

while猜数字实例——C++版

案例描述&#xff1a;系统随机生成一个1到100之间的数字&#xff0c;玩家进行猜测&#xff0c;如果猜错&#xff0c;提示玩家数字过大或过小&#xff0c;如果猜对恭喜玩家胜利并退出游戏。 逻辑框图&#xff1a; #include<bits/stdc.h> using namespace std; int main()…

Redis 持久化——AOF

文章目录 为什么需要AOF?概念持久化查询和设置1. 查询AOF启动状态2. 开启AOF持久化2.1 命令行启动AOF2.2 配置文件启动 AOF 3. 触发持久化3.1 自动触发3.3 手动触发 4. AOF 文件重写4.1 什么是AOF重写&#xff1f;4.2 AOF 重写实现4.3 AOF 重写流程 5. 配置说明6. 数据恢复6.1…

数据结构和算法-交换排序中的快速排序(演示过程 算法实现 算法效率 稳定性)

文章目录 总览快速排序&#xff08;超级重要&#xff09;啥是快速排序演示过程算法实现第一次quicksort函数第一次partion函数到第一次quicksort的第一个quicksort到第二次quicksort的第一个quicksort到第二次quicksort的第二个quicksort到第一次quicksort的第二个quicksort到第…

UnityVR入门之六 如何让3DUI层级在场景模型之上

一、问题来源 根据 UnityVR入门之五 射线检测交互-CSDN博客 这一章节我们了解到VR要与UI交互需要将Canvas设置为World Space属性&#xff0c;然后使用碰撞盒的方式进行射线交互。 正常我们ui是始终叠加在3d场景之上的&#xff0c;如此设置当ui与场景模型相交就会遮挡穿模 二、解…

【Java集合篇】负载因子和容量的关系

负载因子和容量有什么关系 ✔️典型解析✔️loadfactor为啥默认是0.75F&#xff0c;不是1呢?✔️为什么HashMap的默认负载因子设置成0.75✔️0.75的数学依据是什么✔️0.75的必然因素 ✔️HashMap的初始值设为多少合适? ✔️典型解析 HashMap 中有几个属性&#xff0c;如 cap…

使用qtquick调用python程序,pytorch

一. 内容简介 使用qtquick调用python程序 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3pytorch 安装pytorch(http://t.csdnimg.cn/GVP23) 2.4QT 5.14.1 新版QT6.4,&#xff0c;6.5在线安装经常失败&#xff0c;而5.9版本又无法编译64位程序&#xf…

云尚办公项目学习

完整的笔记可以参考这个专栏&#xff0c;写的挺详细的&#xff1a;云尚办公课件笔记&#xff0c;come on boy 文章目录 form-create表设计步骤1&#xff0c;创建审批类型2&#xff0c;创建审批类型下的审批模板3&#xff0c;为指定的审批模板设置模板名称&#xff0c;表单项&am…

Python笔记01-你好Python

文章目录 Python简介环境安装Hello world开发工具 Python简介 python的诞生 1989年&#xff0c;为了打发圣诞节假期&#xff0c;Gudio van Rossum吉多 范罗苏姆&#xff08;龟叔&#xff09;决心开发一个新的解释程序&#xff08;Python雏形&#xff09; 1991年&#xff0c;第…