【JavaEE】常见的锁策略 -- 多线程篇(4)

news2024/10/7 3:28:38

文章目录

  • 乐观锁 vs 悲观锁
  • 读写锁
  • 重量级锁 vs 轻量级锁
  • 自旋锁(Spin Lock)
  • 公平锁 vs 非公平锁
  • 可重入锁 vs 不可重入锁

乐观锁 vs 悲观锁

  • 悲观锁:
    • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
  • 乐观锁:
    • 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

在这里插入图片描述

  • 这两种思路不能说谁优谁劣, 而是看当前的场景是否合适.
  • 如果当前老师确实比较忙, 那么使用悲观锁的策略更合适, 使用乐观锁会导致 “白跑很多趟”, 耗费额外的资源
  • 如果当前老师确实比较闲, 那么使用乐观锁的策略更合适, 使用悲观锁会让效率比较低.

Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.

在这里插入图片描述

乐观锁的一个重要功能就是要检测出数据是否发生访问冲突. 我们可以引入一个 “版本号” 来解决.

  • 假设我们需要多线程修改 “用户账户余额”.

  • 设当前余额为 100. 引入一个版本号 version, 初始值为 1. 并且我们规定 "提交版本必须大于记录当前版本才能执行更新余额

    1. 线程 A 此时准备将其读出( version=1, balance=100 ),线程 B 也读入此信息( version=1, balance=100 )在这里插入图片描述
    1. 线程 A 操作的过程中并从其帐户余额中扣除 50( 100-50 ),线程 B 从其帐户余额中扣除 20 ( 100-20 );在这里插入图片描述
    1. 线程 A 完成修改工作,将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=50 ),写回到内存中;在这里插入图片描述
    1. 线程 B 完成了操作,也将版本号加1( version=2 )试图向内存中提交数据( balance=80 ),但此时比对版本发现,操作员 B 提交的数据版本号为 2 ,数据库记录的当前版本也为 2 ,不满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败.在这里插入图片描述

读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.

  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写一个数据, 有线程安全问题.
  • 一个线程读另外一个线程写, 也有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

其中,

  • 读加锁和读加锁之间, 不互斥.
  • 写加锁和写加锁之间, 互斥.
  • 读加锁和写加锁之间, 互斥.

读写锁特别适合于 “频繁读, 不频繁写” 的场景中. (这样的场景其实也是非常广泛存在的)

Synchronized 不是读写锁.

重量级锁 vs 轻量级锁

锁的核心特性 “原子性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的

  • CPU 提供了 “原子操作指令”.
  • 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁.
  • JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类.

在这里插入图片描述
注意: 注意, synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作

重量级锁: 加锁机制重度依赖了 OS 提供了 mutex

  • 大量的内核态用户态切换
  • 很容易引发线程的调度

这两个操作, 成本比较高. 一旦涉及到用户态和内核态的切换, 就意味着 “沧海桑田”.

轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex.

  • 少量的内核态用户态切换.
  • 不太容易引发线程调度.

在这里插入图片描述
synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.

自旋锁(Spin Lock)

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度.

但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题.

自旋锁伪代码

while (抢锁(lock) == 失败) {}

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来.

一旦锁被其他线程释放, 就能第一时间获取到锁.

自旋锁是一种典型的 轻量级锁 的实现方式.

  • 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.
  • 缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是不消耗 CPU 的).

synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的.

公平锁 vs 非公平锁

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.

当线程 A 释放锁的时候, 会发生啥呢?

  • 公平锁: 遵守 “先来后到”. B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
  • 非公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁.

注意:

  • 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
  • 公平锁和非公平锁没有好坏之分, 关键还是看适用场景.

synchronized 是非公平锁

可重入锁 vs 不可重入锁

  • 可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。

  • 比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。

  • Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。

  • 而 Linux 系统提供的 mutex 是不可重入锁.

在这里插入图片描述
synchronized 是可重入锁

可重入锁实现的理解

  • 如何知道上锁的线程是哪个, 如何知道上锁的线程是否已经上过锁了?
    • 可重入锁, 是让锁记录了当前是哪个线程持有了锁
    • 第一次上锁就是真正的上锁了, 再次上锁就只是增加上锁计数器
  • 如何知道何时解锁?
    • 让锁对象不光要记录时哪个线程持有的锁, 同时在通过一个整形变量记录当前这个线程加了此次锁
    • 每遇到一个加锁操作, 计数器 +1, 每遇到一个解锁操作, 就 -1
    • 当计算器被减为 0 的时候, 才真正的执行释放锁操作, 其它时候不释放

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

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

相关文章

全连接网络参数Xavier初始化

1.梯度消失 考虑下图的神经网络,在使用梯度下降法迭代更新W_ki和W_ij时,它们的梯度方向间有什么关系? 它们的梯度关系如下: 从上述两个式子我们大致可以看出,损失函数L关于第h层参数的梯度由两部分组成:…

sql server2014如何添加多个实例 | 以及如何删除多个实例中的单个实例

标题sql server2014如何添加多个实例 前提(已安装sql server2014 且已有默认实例MSSQLSERVER) 添加新的实例 其实就是根据安装步骤再安装一次(区别在过程中说明) 双击安装 选择“全新独立安装或添加现有功能” 然后下一步下一…

微信小程序开发之后台数据交互及wxs应用

目录 一、后端准备 1. 应用配置 2. 数据源配置 二、数据库 1. 创建 2. 数据表 3. 数据测试 三、前端 1. 请求方法整合 2. 数据请求 3. WXS的使用 4. 样式美化 5. 页面 一、后端准备 通过SpringMVC及mybatis的技术学习,还有前后端分离的技术应用&…

Linux程序地址

目录 一、定义 二、问题引出 三、虚拟地址和物理地址 &#xff08;一&#xff09;问题解释 &#xff08;二&#xff09;什么是进程地址空间 &#xff08;三&#xff09;为什么要有进程地址空间 一、定义 #include <stdio.h> #include <stdlib.h>//geten…

运维监控Zabbix部署

目录 运维监控Zabbix部署 1. 简介 2. 安装 ​编辑 2.1 安装前准备 - Mysql 2.2 安装Zabbix Server 和 Zabbix Agent 2.2.1 安装Zabbix yum库 2.2.2 安装Zabbix Server、前端、Agent 2.2.3 初始化Mysql数据库 2.2.4 为Zabbix Server配置数据库 2.2.5 配置Zab…

【目标检测】Co-DETR:ATSS+Faster RCNN+DETR协作的先进检测器(ICCV 2023)

论文&#xff1a;DETRs with Collaborative Hybrid Assignments Training 代码**&#xff1a;https://github.com/Sense-X/Co-DETR 文章目录 摘要一、简介二、本文方法2.1.概述2.2.协同混合分配训练2.3. 定制的正 Query 生成2.4. Co-DETR为何有效1、丰富编码器的监督2、通过减少…

QEMU DirtyLimit特性介绍

文章目录 背景基本原理PMLDirty-RingDirty-Limit 具体实现数据结构vcpu_dirty_rate_statdirtylimit_state 算法实现接口逻辑qmp_set_vcpu_dirty_limitqmp_cancel_vcpu_dirty_limit 限制算法算法框架理想效果具体实现 测试验证QEMULibvirt 一个广子 背景 热迁移实现逻辑中&…

---图的遍历和最小生成树

广度优先遍历 --- 针对的是顶点遍历 深度优先遍历 如果给的图不是连通图&#xff1f;以某个点为起点就没有遍历完成。那么怎么保证遍历完剩下的点呢&#xff1f;&#xff1f; 在标记数组当中找没有遍历过的点&#xff0c;在进行遍历 最小生成树 生成树&#xff1a;一个连通…

使用TypeScript和jsdom库实现自动化数据抓取

目录 环境准备 使用TypeScript和jsdom抓取数据 总结 随着网络技术的发展&#xff0c;数据抓取已成为获取信息的重要手段。然而&#xff0c;手动进行数据抓取既耗时又容易出错。因此&#xff0c;本文将介绍如何使用TypeScript和jsdom库实现自动化数据抓取。我们将通过创建一个…

iMazing苹果用户手机备份工具 兼容最新的iOS16操作系统

现在距离苹果秋季新品发布会已过去月余&#xff0c;新iPhone 14系列和新版的iOS 16操作系统也如约与我们见面了&#xff0c;相信大家在9月初抢购的iPhone 14也基本到手了&#xff0c;但随之到来的数据资料备份迁移却是一件令人头大的事情&#xff0c;使用官方提供的iTunes软件卡…

计算机网络,网络(OSI)七层模型,三次握手四次挥手,get与post请求区别,网络IO(BIO\NIO\AIO),TCP与UDP区别

1.OSI模型&#xff1f; 开放式系统互联通信参考模型(Open System Interconnection Reference Model) OSI网络七层模型&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 TCP/IP协议群简化了OSI七层模型&#xff1a;应用层、传输层、网络层、数据链路…

java-各种成员变量初始化过程-待完善

前置条件 一、本文章讨论的成员变量 public static final String aa "aa";public static final Integer bb 1;public static final Students cc new Students();public static String aa1 "aa";public static Integer bb1 1;public static String bb2…

nodejs+vue中学信息技术线上学习系统-计算机毕业设计

因此&#xff0c;将现代化的计算机技术、网络技术以及多媒体等技术相结合&#xff0c;开发基于互联网的自主学习平台&#xff0c;为学生提供良好的自主学习环境&#xff0c;方便学生能够网上学习&#xff0c;师生通过该平台可以进行课后交流。目 录 摘 要 I ABSTRACT II 目 录 …

ssrf漏洞学习

目录 ssrf漏洞 相关函数 相关协议 file协议 dict协议 gopher协议 ctfshow ssrf web351 web352 web353 web354过滤01 web355五位长度 web356 三位长度 web357 DNS重定向 web358 正则 ssrf漏洞 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请…

基于SSM的培训学校教学管理平台的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

ASCII_Util.java

package asc_ii;/*** 我写程序&#xff0c;写代码&#xff0c;做项目做产品&#xff0c;更加努力学习做人* 我曾经家里有两只狗&#xff0c;rocket就是那种小型犬吧&#xff0c;两耳朵跑起来飞舞着&#xff0c;我也不记得是不是舞蝶犬* 还有一条中型犬&#xff0c;“豆豆”&…

小程序实现后台数据交互及WXS的使用

一&#xff0c;数据交互准备工作 1.1 后端准备 后端部分代码&#xff0c;可自行创建后端代码 package com.zking.minoa.wxcontroller;import com.zking.minoa.mapper.InfoMapper; import com.zking.minoa.model.Info; import com.zking.minoa.util.ResponseUtil; import org…

FPGA【紫光语法】

寄存器数据类型&#xff1a; reg 默认为 1 bit wide&#xff0c;如果超过 1 bit&#xff0c;则需要 range declaration 设置 reg 的位宽integer 默认位宽为 32 bit&#xff0c;不允许有 range declarationtime 默认位宽为 64 bit&#xff0c;不允许有 range declarat…

黄金现货期货各有各的市场

投资黄金要获得高效的收益&#xff0c;投资者应该选择有一定资金杠杆的保证金品种&#xff0c;比如现货黄金和黄金期货就是这样投资方式&#xff0c;投资者都可以通过它们的杠杆来放大自己的收益&#xff0c;但二者始终存在区别&#xff0c;投资者到底该如何选择呢&#xff1f;…

(2023,DALL-E3,两步微调,标题重建)通过更好的标题改进图像生成

Improving Image Generation with Better Captions 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 重建数据集标题 2.1 构建图像标题器 2.1.1 微调标题器 3…