编程(42)----------锁策略

news2024/12/23 20:10:44

简单总结一下自身对于锁策略的理解.

首先锁策略并非只针对某一种编程语言, 不同的编辑语言都可以使用同一套锁策略. 常见的锁策略有: 

乐观锁和悲观锁

乐观锁, 即认为锁的竞争并非非常激烈. 悲观锁反之. 换句话说, 假设期末来临. 乐观态度的学生认为复习的很好, 问题不大. 而悲观的学生则反复复习, 总怕遗漏知识点. 其次, 乐观锁与悲观锁无法去准确的区分界定, 或者说, 乐观锁与悲观锁是可以相互转变的. 主要就是预测锁竞争是否激烈.

轻量级锁与重量级锁

一般认为线程是轻量级进程. 因为其开销小于进程. 这里的锁也是如此, 轻量级锁上锁解锁开销都小于重量级锁.

自旋锁和挂起等待锁

当我们在开灯的时候, 按下开关, 可能灯并没有亮. 这种情况下, 第一反应是多按几次. 自旋锁跟按开关差不多. 所谓自旋就是会反复尝试去加锁, 直到加上锁为止. 若未加上, 就反复尝试. 因此它的加锁解锁开销都并不大, 是轻量级锁. 但是, 由于要时时刻刻都去尝试加锁, 因此会一直占用一部分系统资源.

挂起等待锁则反之. 开不了灯, 那我就不开了, 等什么时候想起来再来开一下试试. 与自旋锁相对应, 不会一直占用系统资源去尝试加锁, 进而是一个重量级锁.

互斥锁与读写锁

互斥锁一般是有两个操作, 即加锁与解锁, 如果一个线程已经加锁, 另一个线程想要加锁, 就得阻塞等待解锁. 在解锁之前, 加锁与加锁是互斥的.

读写锁则是三个操作, 针对读加锁, 针对写加锁, 以及解锁. 在多线程中, 如果多个线程同时读一个数据, 显然是不会造成线程安全问题的. 因为只读不改, 变量并未改变. 但写操作并非如此. 因此, 如果使用互斥锁就得将整个读写代码都给加锁, 而使用读写锁, 可以针对代码中的写部分单独加锁.

公平锁与非公平锁

这里涉及到顺序问题. 在众多线程中, 先到的线程先加锁, 即为公平锁. 但是这样实现起来其实极其麻烦, 可能得搞一个队列来存放每一个线程任务. 因此大多操作系统都提供的是非公平锁, 雨露均沾. 加锁依赖于线程调度的顺序, 调度顺序本来就是随机的, 不会考虑谁先到谁后到的问题.

可重入锁与不可重入锁.

这个就是看其能不能加锁之后再套一个锁.

以synchronized为例, 描述任何编程语言的任何一个锁都可以以上为修饰词描述: 它是既是悲观锁也是乐观锁; 既是轻量级锁也是重量级锁; 可以自旋可以挂起; 是互斥锁; 是非公平锁; 也是可重入锁.

接着来说一下CAS(compare and swap)

即比较并交换. 其原理是, 比较A C二者的值的大小, 若相同, 则将其中一个值替换为另一个值B. 若不同则无事发生.

很简单易懂的原理. 但是其最大特点是: 整个CAS过程是不可分割的. 也就是说其具有原子性. 多线程中加锁, 就是为了避免非原子性的读写操作造成线程安全. 而CAS可以有效避免这种情况. 因此, CAS的一大用武之地就是实现原子类:

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                count.getAndIncrement();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

 在这个代码中, 由于实现了原子类, 不再需要对线程加锁. 从而就能得到准确的结果. 

                                                                          

假设两个线程同时执行到读入. 由于CAS本身具有原子性, 故CAS只能在不同线程中一个个执行. 在线程2中先判定出, 值与预估值相同, 故要将值交换为1. 随后到线程1执行时, 比较发现与预估值不同, 则无事发生, 继续往后执行.

但是要注意, 即便CAS与加锁相比似乎更为方便, 可其只属于特殊情况特殊处理的特殊方法. 相比于synchronized加锁而言, 使用范围要小得多, 因此更多时候还是考虑使用加锁这个通用方法.

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

第二个用法就是实现自旋锁. 以一段伪代码来大致描述其过程, 因为是伪代码, 拿到Idea上肯定是会报错的. 在CAS实现自旋锁的时候, 会进行判定, 判定当前的owner是否为空, 而owner是用来判定是否加锁的, 若为空即为未加锁. 因此, 为空时与判定值相同, 就进行交换把当前线程的引用赋值给owner, 即加锁成功, 返回true, 从而结束循环. 若owner不为null, 即锁已被别的线程占用, 返回false, 循环判定而实现自旋.

CAS也存在一定问题. 最典型的就是ABA问题. 假设存入的值是A, 预期值为C, 修改值为B. 也就是说, 先将A与C进行比较, 若二者不同, 令A等于B, 以此实现交换. 那在这里完全可能存在一种极端情况, 有没有可能A通过CAS后改成了B, 后又改成了A? 这样说可能过于抽象, 举一个极端的例子:

平常超市购物结账的时候, 一般都是手机展示付款码, 收银员扫码. 假设有一天去购物, 微信钱包中余额为一千, 购物花费五百. 那么在付款的时候, 就可以视为CAS的过程, 即A为1000, C为1000, B为500, 扫码完成后, 按照流程, 微信钱包显示的余额应该改为500. 这就相当于是AC相比, 相同, 再将C的值赋给A, 即将余额改为500.

但是假如第一次扫码时出现某种不可抵抗的意外导致为弹出已付款的提示, 导致收银员误以为没有扫上码从而再扫一次. 也就是说总共扫了两次. 但由于CAS的特性, 第二次的A已改为500, 与C不同, 因此无事发生. 

但假设刚好这个时候, 有人往该微信账户转账五百呢? 这个时候就出问题了, 扫两次, CAS两次发现A都为1000, 那就可能会发生二次扣款. 当然这只是假设, 真正的扣款操作可能不是CAS原理达成的.

                                                                               

这种情况发生概率极小, 但是不排除其发生的可能性. 解决方法也很简单, 加一个版本号即可. 每次CAS都将版本号+1予以区分. 不以金额为准而以版本号为准, 确保万无一失.

两个线程中, synchronized在对同一个对象加锁时, 必然会出现阻塞等待. 虽然这个等待是必须的. 但是必然会一定程度上降低效率, 因此其内部存在一些优化机制.

第一个是锁升级机制.

锁升级分为几个阶段: 无锁, 偏向锁, 轻量级锁, 重量级锁.

当代码执行到有synchronized时, 就会由无锁转为偏向锁. 偏向锁实际上还并未加锁, 只是占了个位置, 有加锁的趋向. 直到发生锁竞争后, 就会转为轻量级锁, 也就是自旋锁. 如果自旋尝试重新加锁发现迟迟都未解锁, 则会转为重量级锁也就是挂起等待锁. 大概是这么一个过程. 但要注意, 锁可以升级, 却不能降级.

举一个不太恰当的例子来描述一个过程. 假设我喜欢一个妹子, 这个妹子也是单身, 也就是无锁状态. 这个时候我主动出击. 但是我不想确定男女关系, 因为确定关系后对她加了锁, 别的男性不能靠近她, 同时也是对我自己加锁, 这样我就没法接近其他妹子. 因此, 我跟她搞暧昧, 迟迟不表白. 虽然经常跟她一起出去玩, 但关系一直没捅破. 结果时间一长, 妹子感觉我是渣男故意钓着她. 反手跟别的男生表白, 被加上了锁. 

但是我又忘不掉她, 于是每天反反复复的对她嘘寒问暖. 盼望着她能分手解锁, 好让我上位. 这个时候我就是轻量级锁, 反复判定是否解锁, 若解锁, 就直接表白上锁. 结果时间一长,我发现这妹子好像无动于衷, 没有要解锁的迹象. 那我何必在这里浪费时间, 天天嘘寒问暖还不如去干些别的事, 于是我就变成重量级的挂起等待锁, 想起来了再来看看有没有解锁. 大概就是这么个过程.

第二个锁升级机制是锁消除

即编译器智能判定是否需要加锁. 若不需要加锁而程序员加了锁, 就会自动把锁给销毁掉. 典型的案例就是StringBuffer的关键方法都带有锁, 但实际上在有些算法题中使用时, 是将其剔除掉的. 因为单线程编程不存在加不加锁的问题. 加了也是浪费资源.

第三个锁升级机制是锁粗化

这里涉及到锁的粒度这一概念. synchronized所包含的代码越多, 则粒度越粗. 一般来说粒度越细越好. 这不难理解, 因为越多意味着不能并发执行的代码就越多. 这样执行的效率就越慢. 但如果加锁与加锁之间间隙比较短呢? 这就好比网购. 买三样东西, 一般都是选好了后一起付钱. 而不是一个个付钱付三次. 同样的道理, 加锁与解锁都是非常消耗系统资源的. 如果两此加锁间隙过小, 就直接搞一个粗化的锁. 这样比解锁再加锁更节省资源.

-----------------------------------最后编辑于2023.6.16

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

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

相关文章

Navicat 连接 MySQL :2002 - Can‘t connect to server on ‘127.0.0.1‘ (36)

问题&#xff1a; 2002 - Can‘t connect to server on ‘127.0.0.1‘ (36) 场景 Navicat 16MySQL 5.7Windows虚拟机Mac主机 导言&#xff1a; Navicat 是一款功能强大的数据库管理工具&#xff0c;但在使用过程中可能会遇到连接 MySQL 数据库时出现 “Can’t connect to se…

微服务开发系列 第十篇:Gateway

总概 A、技术栈 开发语言&#xff1a;Java 1.8数据库&#xff1a;MySQL、Redis、MongoDB、Elasticsearch微服务框架&#xff1a;Spring Cloud Alibaba微服务网关&#xff1a;Spring Cloud Gateway服务注册和配置中心&#xff1a;Nacos分布式事务&#xff1a;Seata链路追踪框架…

2023年餐饮连锁行业研究报告

第一章 行业概况 餐饮连锁行业是全球经济的重要组成部分&#xff0c;它的主要运营模式是通过连锁经营形式提供食品和饮料服务。 连锁经营主要分为三种运营模式&#xff1a;直营连锁、特许经营和自由连锁经营。三种模式通过专业分工、集中管理实现规模效益。 直营连锁&#x…

华为又开始放大招了?CV新架构:VanillaNet: the Power of Minimalism in Deep Learning 论文阅读笔记

华为又开始放大招了&#xff1f;CV新架构&#xff1a;VanillaNet: the Power of Minimalism in Deep Learning 论文阅读笔记 一、Abstract二、引言三、单个 Vanilla 的神经结构四、训练 VanillaNet4.1 深度训练策略4.2 Series Informed Activation Function 五、实验5.1 消融实…

SSD202D-logo显示调试

参考网址:cBOOT LOGO以及显示参数使用参考 - SigmaStarDocsSigmaStar Developer Documentationhttp://wx.comake.online/doc/doc/TAKOYAKI_ZH/customer/development/software/BOOTLOGO.html1.mipii点亮过程中需要加点复位 2.编译生成新屏参 3.修改屏参选择名字 4.

Cortex-M3 的 双堆栈MSP和PSP

什么是栈&#xff1f; 在谈M3堆栈之前我们先回忆一下数据结构中的栈。栈是一种先进后出的数据结构(类似于枪支的弹夹&#xff0c;先放入的子弹最后打出&#xff0c;后放入的子弹先打出)。M3内核的堆栈也不例外&#xff0c;也是先进后出的。栈的作用&#xff1f; …

性能测试基础知识(一)性能测试的分类

性能测试的分类 一、什么是性能测试&#xff1f;二、性能测试的目的三、性能测试的分类1、基准测试2、并发测试3、负载测试4、压力测试5、其他测试 一、什么是性能测试&#xff1f; 性能测试是在一定的负载1条件下&#xff0c;系统的响应时间等特性是否满足特定的性能需求。需…

Appium 并发多进程基于 Pytest框架详解

目录 前言&#xff1a; 改造思路&#xff1a; 实现&#xff1a; 最后&#xff1a; 总结&#xff1a; 前言&#xff1a; 之前通过重写unittest的初始化方法加入设备参数进行并发&#xff0c;实现了基于unittest的appium多设备并发&#xff0c;但是考虑到unittest的框架实在…

幼儿园门禁如何应用人脸识别技术?3大优势你知道几个

随着社会的发展和科技的进步&#xff0c;人脸识别技术逐渐渗透到各个领域&#xff0c;为我们的生活带来了许多便利和安全。在幼儿园这个特殊的场所&#xff0c;保证幼儿的安全和管理是至关重要的。 通过人脸识别技术&#xff0c;幼儿园可以准确、快速地辨识幼儿、家长和教职工的…

yolov8 目标检测与跟踪

参考&#xff1a; 参考&#xff1a; https://github.com/ultralytics/ultralytics https://github.com/TommyZihao/Train_Custom_Dataset/blob/main/%E7%9B%AE%E6%A0%87%E8%BF%BD%E8%B8%AA/%E5%85%AC%E5%BC%80%E8%AF%BE/ https://www.rstk.cn/news/42041.html?actiononClick …

Docker部署gitlab-runner

gitlab-runner 1.部署 Linux使用二进制的方式Docker中使用容器的方式启动gitlab-runnerHelm包的方式安装gitlab-runner Docker中使用容器的方式启动gitlab-runner 1.安装gitlab runner docker run -d --name gitlab-runner --restart always \ -v /srv/gitlab-runner/conf…

基于U-Net网络实现图像分割

目录 1、作者介绍2、U-Net网络及数据集介绍2.1 U-Net网络2.2 数据集介绍2.2.1 VOC_2012数据集2.2.2 眼球毛细血管数据集2.2.3 医学图像数据集 3、U-Net实现图像分割3.1 U-Net实现图像分割实验&#xff08;简易版本&#xff09;3.1.1 环境配置3.1.2 数据集准备3.1.3 代码实现3.1…

《项目实战》使用JDBC手写分库

文章目录 1、概要2、整体架构流程3、技术名词解释4、技术细节4.1、指定分库规则4.2、安装Mysql数据库以及建库建表4.3、创建Java项目4.3.1、使用 Idea创建Maven项目4.3.1.1、修改pom.xml配置 4.3.2、编写分库/路由规则 DbRouter4.3.3、编写数据库交互工具 DaoUtil4.3.4、编写数…

MyBits的创建与使用

文章目录 前言MyBits的优点这里简单回忆下用JDBC的流程 MyBits的调用流程MyBits的配置传递参数之# 与 $ 的区别 当mysql与程序属性映射不一致时的解决方案 前言 上篇博客讲述了 Spring后端与前端进行交互的过程, 而这篇博客将讲述Spring与数据库的交互 , 众所周知 后端与数据库…

1.1数据结构绪论

一、数据结构 学习如何使用程序代码把现实世界的问题信息化 二、数据的基本概 1、数据&#xff1a;信息的载体&#xff0c;是描述客观世界属性的数、字符及被计算机程序识别和处理的集合。 早期计算机处理的数据——纯数值类型&#xff1b;现代计算机处理数据——非数据类型 …

融合创新:AI虚拟数字人与3D VR全景引领未来旅游潮流

导语&#xff1a; 随着科技不断发展&#xff0c;AI虚拟数字人和3D VR全景技术的融合正引领着创新的潮流。这种融合不仅仅是对传统导览的升级&#xff0c;更为各个领域带来了全新的创新应用。让我们一起探索AI虚拟数字人与3D VR全景融合的创新应用&#xff0c;看看它们如何在多…

快速解决Github无法访问的问题

Github访问慢&#xff0c;是困扰很多人的问题&#xff0c;今天就出一个解决方案&#xff0c;按照下面思路&#xff0c;可以实现快速访问Github&#xff0c;来查看我们需要的资源。 目录 一、获取DNS 二、修改hosts文件内容 2.1 修改hosts权限 2.2 修改hosts内容 三、轻…

以指标驱动,企业数智化迈向新阶段

近年来&#xff0c;我国数字经济蓬勃发展&#xff0c;数据成为推动经济社会发展的新要素。国家十四五规划指出&#xff0c;要激活数据要素潜能&#xff0c;加快建设数字经济&#xff0c;需要重点实施“上云用数赋智”行动&#xff0c;推动数据赋能全产业链协同转型。为进一步迈…

保姆级教你用Python制作超级玛丽游戏“爷青回~”(文末赠书)

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 贪吃蛇游戏 弹珠游戏 超级玛丽&#xff08;爷青回~&#xff09; 完整代码如下&#xff1a; 总…

SpringBoot 实现 PDF 添加水印

SpringBoot 实现 PDF 添加水印 使用场景方式一&#xff1a;使用 Apache PDFBox 库方式二&#xff1a;使用 iText 库方式三&#xff1a;Free Spire.PDF for JavaDemo 使用场景 PDF&#xff08;Portable Document Format&#xff0c;便携式文档格式&#xff09;是一种流行的文件…