《Java-SE-第二十七章》之常见的锁策略

前言

在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!”

博客主页:KC老衲爱尼姑的博客主页

博主的github,平常所写代码皆在于此

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


文章目录

  • 常见的锁策略
    • 乐观锁vs悲观锁
    • 读写锁
    • 重量级锁vs轻量级锁
    • 自旋锁vs挂起等待锁
    • 公平锁vs非公平锁
    • **可重入锁** **vs** **不可重入锁**

常见的锁策略

乐观锁vs悲观锁

 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

乐观锁:假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并

发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

 举个栗子:合租房里面有2个人一起合租,厕所只有一个。A在上厕所的时候,就比较乐观,他上厕所的时候,他就觉得B想上厕所的概率比较小,就不锁门了。这就是乐观锁。所谓的悲观锁就是,B上厕所的时候,就比较悲观,就觉得A懒人屎尿多,老想上厕所,就每次上厕所的时候,即使A不在家,也会把门锁上。

读写锁

 多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

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

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

  • 两个线程都要写一个数据, 有线程安全问题.

  • 一个线程读另外一个线程写, 也有线程安全问题.

 Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读锁写锁,ReentrantReadWriteLock.ReadLock 类表示一个读锁.,这个对象提供了 lock / unlock 方法进行加锁解锁。 ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁。其中读加锁和读加锁不互相互斥,写加锁和写加锁之前互斥,读加锁和写加锁之间互斥。

使用演示

 我们创建了一个ReadWriteLockExample类,其中包含了一个sharedData变量,表示共享数据。ReadWriteLock用于控制对sharedData的读写访问。

 读取操作使用读锁(readLock)进行保护,允许多个线程同时读取共享数据。写入操作使用写锁(writeLock)进行保护,确保在写入过程中只有一个线程能够修改共享数据。

 在main方法中,我们创建了多个读取线程和一个写入线程。读取线程会不断读取共享数据并输出,而写入线程会每隔一段时间写入一个新的数据。由于使用了读写锁,读取线程可以并发执行读操作,而写入线程则可以互斥地执行写操作,从而实现了对共享数据的安全读写。

实现代码


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private int sharedData = 0;private ReadWriteLock rwLock = new ReentrantReadWriteLock();private Lock readLock = rwLock.readLock();private Lock writeLock = rwLock.writeLock();public void readData() {readLock.lock();try {System.out.println("Read Thread: Reading data: " + sharedData);} finally {readLock.unlock();}}public void writeData(int data) {writeLock.lock();try {System.out.println("Write Thread: Writing data: " + data);sharedData = data;} finally {writeLock.unlock();}}public static void main(String[] args) {ReadWriteLockExample example = new ReadWriteLockExample();// 创建多个读取线程for (int i = 0; i < 5; i++) {new Thread(() -> {while (true) {example.readData();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 创建一个写入线程new Thread(() -> {int data = 1;while (true) {example.writeData(data);data++;try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

运行结果:

在这里插入图片描述

重量级锁vs轻量级锁

 锁的核心特性 “原子性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的,CPU 提供了 "原子操作指令,然后操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁.,JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类。

重量级锁

 重量级锁 加锁机制重度依赖了 OS 提供了 mutex,使用此锁容易引起线程的调度以及大量的内核态用户态的切换。这两个操作成本都比较高,一旦涉及到用户态和内核态的切换,就意味着"沧海桑田",典型的进入内核态的加锁逻辑,开销比较大。

轻量级锁

 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成. 实在搞不定了, 再使用 mutex,轻量级锁和重量级锁相反,不太容易引起线程调度以及只有少量的内核态用户态的切换,典型的纯用户态的加锁逻辑,开销比较小。

 举个栗子,去银行办理业务,在窗口找机器办理,自己操作,这里是用户态,用户态的时间成本相对是可控的。有时候得去排队找工作人员办理,这就是内核态.内核态的时间成本不太可控,因为办理业务的时候存在和工作人员大量的沟通,还需要排队,这时的效率就很低。

synchronized 开始是一个轻量级锁 如果锁冲突比较严重,就会变成重量级锁

自旋锁vs挂起等待锁

自旋锁

 按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度,但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU。 这个时候就可以使用自旋锁来处理这样的问题.。

自旋锁伪代码

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

 如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来.。一旦锁被其他线程释放, 就能第一时间获取到锁。

 举个栗子,这就还比张三和妹子出去约会,当张三已经到了目的地,张三就给妹子打电话,问到了没,妹子就说"马上",挂了之后,你又不停的打电话询问到了没。这样你就第一时间知道她到了。

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

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.

缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是不消耗 CPU 的).

挂起等待锁

当获取锁失败后,就会挂起等待。

 举个栗子,依旧是张三和妹子约会的,张三不再反复的打电话询问妹子到了没,而是站在那里看小说等待着妹子过来。

公平锁vs非公平锁

 假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.。当线程 A 释放锁的时候, 会发生啥呢?

公平锁: 遵守 “先来后到”. B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.。

非公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁.

 举个栗子,当被一群男生心心念念的女神失恋后,这些男生对女生都展开了猛烈的追求,先来的男生先上位,这就是公平锁。如果是女神看那个顺眼就和那个在一起,这就是非公平锁。

使用ReentrantLock演示公平锁和非公平锁,ReentrantLock默认是非公平锁

代码演示


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockFairnessDemo {private static Lock fairLock = new ReentrantLock(true); // 公平锁private static Lock nonFairLock = new ReentrantLock(); // 非公平锁public static void main(String[] args) {Runnable fairTask = () -> {fairLock.lock();try {System.out.println("Fair lock: Thread " + Thread.currentThread().getId() + " acquired the lock.");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {fairLock.unlock();}};Runnable nonFairTask = () -> {nonFairLock.lock();try {System.out.println("Non-fair lock: Thread " + Thread.currentThread().getId() + " acquired the lock.");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {nonFairLock.unlock();}};// 使用公平锁执行任务for (int i = 0; i < 5; i++) {new Thread(fairTask).start();}try {Thread.sleep(10000); // 等待2秒,以确保公平锁的线程先运行} catch (InterruptedException e) {e.printStackTrace();}System.out.println("------------------");// 使用非公平锁执行任务for (int i = 0; i < 5; i++) {new Thread(nonFairTask).start();}}
}

运行结果:

在这里插入图片描述

 当运行程序时,你会注意到公平锁的输出中,线程获得锁的顺序与线程启动的顺序一致。这是公平锁保证的特性。而在非公平锁的输出中,线程的获得锁顺序与线程启动顺序不一致,这是因为非公平锁在某些情况下允许新线程抢占锁,以提高并发性能。

可重入锁 vs 不可重入锁

 可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。不可重入锁就是同一个线程多次获取同一把锁,把自己锁死。


各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。

在这里插入图片描述

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

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

相关文章

RD算法(四)登堂入室 —— 成像完成

SAR成像专栏目录_lightninghenry的博客-CSDN博客https://lightning.blog.csdn.net/article/details/122393577?spm=1001.2014.3001.5502先放RD算法最终的成像结果: 经简单的地距投影后为(地距投影的内容在后面的几何校正章节中讲解): 温哥华这地形还真像是一张怪兽的巨嘴呀…

方差分析||判断数据是否符合正态分布

方差分析练习题 练习学习笔记&#xff1a; &#xff08;1&#xff09; 标准差和标准偏差、均方差是一个东西。标准误差和标准误是一个东西。这两个东西有区别。 &#xff08;2&#xff09;单因素方差分析&#xff08;MATLAB求解&#xff09; &#xff08;3&#xff09;使用an…

【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

同比环比使用方法

一、解释&#xff1a; 1.同比&#xff1a;本期与去年同期相比 &#xff0c;如2023年8月 比 2022年8月 2.环比&#xff1a;本期与上期相比 &#xff0c;如2023年8月 比 2023年7月 二、应用&#xff1a; 1.场景&#xff1a;统计日报、周报、月报、年报下进店客流的同比和环…

银河麒麟V10 飞腾 Qt环境搭建

采用在线安装方式&#xff1a; 1、在线安装qt组件 sudo apt-get install qt5-* 2、在线安装qt creator sudo apt-get install qtcreator 以上简单两步安装完成后&#xff0c;新建项目已经可以编译过&#xff0c;但ClangCodeModel会报错如下图 the code model could not parse …

开始学习 Kafka,一文掌握基本概念|Kafka 系列 一

如果你还不了解 Kafka&#xff0c;或者也打算深入探索、系统学习&#xff0c;那么欢迎有同样目标的小伙伴可以加群交流&#xff0c;让学习之路不再孤独。 一个人可能走的很快&#xff0c;但是一群人会走的更远。&#xff08;后台回复&#xff1a;加群&#xff09; 点击上方“后…

python

文章目录 初识pythonpython的安装win系统Linux系统&#xff08;centos7&#xff09; 第一个Python程序常见问题 Python解释器Python开发环境PyCharm的基础使用创建项目修改主题修改默认字体和大小汉化插件翻译软件常用快捷键 初识python Python语言的起源可以追溯到1989年&…

Ubuntu-文件和目录相关命令

&#x1f52e;linux的文件系统结构 ⛳目录结构及目录路径 &#x1f9e9;文件系统层次结构标准FHS Filesystem Hierarchy Standard(文件系统层次结构标准&#xff09; Linux是开源的软件&#xff0c;各Linux发行机构都可以按照自己的需求对文件系统进行裁剪&#xff0c;所以众多…

17. Spring 事务

目录 1. 事务定义 2. MySQL 中的事务使用 3. 没有事务时的插入 4. Spring 编程式事务 5. Spring 声明式事务 5.1 Transactional 作用范围 5.2 Transactional 参数说明 5.3 Transactional 工作原理 1. 事务定义 将⼀组操作封装成一个执行单元&#xff08;封装到一起…

ffplay——QT项目移植

一、ffmpeg源码编译 参考&#xff1a; https://blog.csdn.net/sgzed/article/details/119850119 在生成时做了一些修改&#xff1a; ./configure --toolchainmsvc --enable-shared --enable-postproc --enable-gpl --prefixwindows 二、对文件做调整 ffplay只需要三个文件&…

stm32常见数据类型

stm32的数据类型的字节长度 s8 占用1个byte&#xff0c;数据范围 -2^7 到 (2^7-1) s16 占用2个byte&#xff0c;数据范围 -2^15 到 (2^15-1) s32 占用 4个byte&#xff0c;数据范围 -2^31 到 (231-1)231 2147483647 int64_t占用8个byte&#xff0c;数据范围 -2^63 到 (2^63-1)…

一文快速入门Byzer-python

目录 一、Byzer-Python介绍 二、Byzer-python工具语法糖 三、环境依赖 1. Python 环境搭建 2. Ray 环境搭建 3. Byzer-python 与 Ray 四、参数详解 五、数据处理 1. Byzer-python 处理数据 2. Byzer-python 代码说明 3. Byzer-python 读写 Excel 文件 4. Byzer-pytho…

如何搭建一个口才培训的网站?需要具备哪些条件?

论文题目&#xff1a;如何搭建一个口才培训的网站及所需条件 摘要&#xff1a; 本文探讨了如何搭建一个口才培训的网站&#xff0c;并详细分析了所需的关键条件。口才培训作为一种重要的社交技能&#xff0c;能够帮助人们提升自信和影响力&#xff0c;因此具有广阔的市场前景。…

day17 | 654.最大的二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

文章目录 一、最大的二叉树二、合并二叉树三、二叉搜索树中的搜索四、验证二叉搜索树 一、最大的二叉树 654.最大的二叉树 构建二叉树的题目&#xff0c;都用前序遍历。 因为我们一定要先构建根节点&#xff0c;才能继续向后构建。 递归函数的参数和返回值&#xff1a; Tree…

AssetBundle学习

官方文档&#xff1a;AssetBundle 工作流程 - Unity 手册 (unity3d.com) 之前写的博客&#xff1a;AssetBundle学习_zaizai1007的博客-CSDN博客 使用流程图&#xff1a; 1&#xff0c;指定资源的AssetBundle属性 &#xff08;xxxa/xxx&#xff09;这里xxxa会生成目录&…

redux-promise-middleware和applyMiddleware的理解与使用

一、作用&#xff1a; applyMiddleware是一个中间件&#xff0c;通常和applyMiddleware结合使用&#xff0c;是dispatch与reducers之间的应用&#xff0c;用于处理dispatch发送的异步action操作 二、使用 1、安装redux-promise-middleware cnpm i redux-promise-middleware…

Micropython STM32F4入门点灯第一课

Micropython STM32F4入门点灯第一课 &#x1f4cc;固件刷可参考前面一篇《STM32刷Micropython固件参考指南》&#x1f4cd;固件下载&#xff1a;https://micropython.org/download/?mcustm32f4&#x1f516;本例程基于STM32F4DISC&#xff0c;主控芯片STM32F407VGT6&#x1f4…

vue 混入(mixin)的使用

在 vue 组件内&#xff0c;如果想将一些公共功能&#xff0c;如组件、方法、钩子函数等复用&#xff0c;混入是一个很好的选择。 现在开始我们的混入使用吧 1、我们可以创建一个目录mixins&#xff0c;在创建一个comment.js文件如图&#xff1a; // 在 common.js 里写你想共享…

20230803激活手机realme GT Neo3

20230803激活手机realme GT Neo3 缘起&#xff1a; 新买的手机&#xff1a;realme GT Neo3 需要确认&#xff1a; 1、4K录像&#xff0c;时间不限制。 【以前的很多手机都是限制8/10/12/16分钟】 2、通话自动录音 3、定时开关机。 4、GPS记录轨迹不要拉直线&#xff1a;户外助…

小程序学习(四):WXML模板语法

WXML模板语法-数据绑定 1.数据绑定的基本原则 ①在data中定义数据 ②在WXML中使用数据 2.动态绑定属性 WXML模板语法-事件绑定 3.什么是事件 4.小程序中常用的事件 5.事件对象的属性列表 6.target和currentTarget的区别 7.bindtap的语法格式 8.在事件处理函数中为data中的数据…