并发编程学习案例-ReentrantReadWriteLock非公平的情况下读锁插队和写锁插队场景复现

article/2023/12/3 2:47:05

文章目录

    • 一、前言
    • 二、源码
    • 三、 代码案例
      • (一)复现写的时候插队场景
        • 参考执行结果
      • (二)复现读的时候插队
      • 参考执行结果
    • 参考资料

一、前言

Java ReentrantReadWriteLockReadWriteLock 的实现类,可以分出2把锁,ReentrantReadWriteLock.ReadLock 读锁和 ReentrantReadWriteLock.WriteLock写锁。我们知道读读之间可以共享,读写、写写是互斥的,这样并发度比 ReentrantLock 这种互斥锁更高。ReentrantReadWriteLock在Java 中锁分公平和非公平:

  • 在公平的场景下,多个线程会排队按顺进行加锁和释放锁;
  • 在非公平的场景下如果排队中的线程在唤醒期间还未唤醒时,此时如果有其它写线程加入则无需进入对列等待可插队获取到锁;如果在唤醒期间还未唤醒时,排队的队列里头节点是读线程,此时如果有其它读线程加入则无需进入对列等待可插队获取到读锁;。这里复现 ReentrantReadWriteLock 读锁在特定的场景下插队的场景复现

总结:

  • 写锁可以随时插队
  • 读锁仅在等待对列头节点不是想获取写锁的线程的时候可以进行插队

二、源码

ReentrantReadWriteLock 的源码里非公平的实现,writeShouldBlock()写总能插队,readerShouldBlock()读在当前对列第一个线程是排它时,可以进行插队,下面给出代码演示案例

在这里插入图片描述

三、 代码案例

(一)复现写的时候插队场景

依次启动线程一(写线程),线程二线程三(读线程),线程四(写线程),线程五线程六(读线程),然后再启动一个子线程来创建1K个写线程进行尝试写。
线程一先获取写锁,其它线程则进入对列阻塞等待,当线程一处理完毕释放锁的瞬间,如果有非中文的写线程在线程二三四五六前先获取到写线程,则说明,这些写线程进行了插队。

package com.lvzb.lock;import org.junit.jupiter.api.Test;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 非公平的读写锁,写锁插队 和 读锁插队案例** @author: lvzb31988* @date: 2023/02/03 17:16*/
public class NofaireBargeDemoTest {private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();public void read() {System.out.println(Thread.currentThread().getName() + "尝试获取读锁");readLock.lock();try {TimeUnit.MILLISECONDS.sleep(20);System.out.println(Thread.currentThread().getName() + " 获取到了读锁...");} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}public void write() {System.out.println(Thread.currentThread().getName() + "尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + " 获取到了写锁...");TimeUnit.MILLISECONDS.sleep(45);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + " 释放到了写锁...");writeLock.unlock();}}/*** 模拟出来 写锁插队的场景** @throws InterruptedException*/@Testvoid nofairTest1() throws InterruptedException {NofaireBargeDemoTest bargeDemo = new NofaireBargeDemoTest();List<Thread> arr = new ArrayList<>();new Thread(bargeDemo::write, "线程一").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程二").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程三").start();Thread.sleep(5);new Thread(bargeDemo::write, "线程四").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程五").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程六").start();new Thread(() -> {for (int i = 0; i < 800; i++) {new Thread(bargeDemo::write, "线程" + i).start();}}).start();TimeUnit.SECONDS.sleep(50);}}

参考执行结果

日志打印结果 第一个写线程释放瞬间,被其376号刚创建的写线程给获取执行,而没有直接执行队列里早已等待的读线程二和三


线程一尝试获取写锁
线程一 获取到了写锁...
线程二尝试获取读锁
线程三尝试获取读锁
线程四尝试获取写锁
线程五尝试获取读锁
线程六尝试获取读锁
线程0尝试获取写锁
线程1尝试获取写锁
线程2尝试获取写锁
线程3尝试获取写锁
线程4尝试获取写锁
线程5尝试获取写锁
线程6尝试获取写锁
线程7尝试获取写锁
线程8尝试获取写锁
..........
线程371尝试获取写锁
线程289尝试获取写锁
线程373尝试获取写锁
线程374尝试获取写锁
线程375尝试获取写锁
线程一 释放到了写锁...
线程376尝试获取写锁
线程377尝试获取写锁
线程376 获取到了写锁...
线程161尝试获取写锁
线程474尝试获取写锁
线程473尝试获取写锁
线程472尝试获取写锁
线程475尝试获取写锁
..........
线程768尝试获取写锁
线程792尝试获取写锁
线程790尝试获取写锁
线程793尝试获取写锁
线程794尝试获取写锁
线程758尝试获取写锁
线程789尝试获取写锁
线程784尝试获取写锁
线程776尝试获取写锁
线程786尝试获取写锁
线程779尝试获取写锁
线程760尝试获取写锁
线程781尝试获取写锁
线程778尝试获取写锁
线程783尝试获取写锁
线程788尝试获取写锁
线程785尝试获取写锁
线程787尝试获取写锁
线程780尝试获取写锁
线程376 释放到了写锁...
线程二 获取到了读锁...
线程三 获取到了读锁...
线程四 获取到了写锁...
线程四 释放到了写锁...
线程五 获取到了读锁...
线程六 获取到了读锁...
线程0 获取到了写锁...
线程0 释放到了写锁...
线程1 获取到了写锁...
.......

(二)复现读的时候插队

依次启动线程一(写线程),线程二线程三(读线程),线程四(写线程),线程五线程六(读线程),然后再启动一个子线程来创建1K个读线程进行尝试写。
线程一先获取写锁,其它线程则进入对列阻塞等待,当线程一处理完毕释放锁的瞬间,如果中文的读线程二获取到读锁进行执行此时等待对列的头部是线程三,在三还没开始去读期间如果有非中文的读线程进行了读操作,则说明,这些读线程进行了插队。

package com.lvzb.lock;import org.junit.jupiter.api.Test;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 非公平的读写锁,写锁插队 和 读锁插队案例** @author: lvzb31988* @date: 2023/02/03 17:16*/
public class NofaireBargeDemoTest {private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();public void read() {System.out.println(Thread.currentThread().getName() + "尝试获取读锁");readLock.lock();try {TimeUnit.MILLISECONDS.sleep(20);System.out.println(Thread.currentThread().getName() + " 获取到了读锁...");} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock();}}public void write() {System.out.println(Thread.currentThread().getName() + "尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + " 获取到了写锁...");TimeUnit.MILLISECONDS.sleep(45);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + " 释放到了写锁...");writeLock.unlock();}}/*** 模拟读线程在队列头部是读线程时,新的读线程可以进行插队场景,* 大量的读线程需要在子线程里创建,主线程内创建模拟不出来** @throws InterruptedException*/@Testvoid nofairTest() throws InterruptedException {NofaireBargeDemoTest bargeDemo = new NofaireBargeDemoTest();List<Thread> arr = new ArrayList<>();new Thread(bargeDemo::write, "线程一").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程二").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程三").start();Thread.sleep(5);new Thread(bargeDemo::write, "线程四").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程五").start();Thread.sleep(5);new Thread(bargeDemo::read, "线程六").start();new Thread(() -> {for (int i = 0; i < 1000; i++) {new Thread(bargeDemo::read, "线程" + i).start();}}).start();TimeUnit.SECONDS.sleep(50);}
}

参考执行结果

从结果可以看到很多数字类型的读线程在写线程一释放锁后,抢在了读线程二和线程三之前进行插队

线程一尝试获取写锁
线程一 获取到了写锁...
线程二尝试获取读锁
线程三尝试获取读锁
线程四尝试获取写锁
线程五尝试获取读锁
线程六尝试获取读锁
线程0尝试获取读锁
线程2尝试获取读锁
线程1尝试获取读锁
线程4尝试获取读锁
线程5尝试获取读锁
线程9尝试获取读锁
线程3尝试获取读锁
........
线程363尝试获取读锁
线程422尝试获取读锁
线程365尝试获取读锁
线程一 释放到了写锁...
线程428尝试获取读锁
线程426尝试获取读锁
线程320尝试获取读锁
线程369尝试获取读锁
线程432尝试获取读锁
线程427尝试获取读锁
线程421尝试获取读锁
线程366尝试获取读锁
线程412尝试获取读锁
线程431尝试获取读锁
线程413尝试获取读锁
线程398尝试获取读锁
线程430尝试获取读锁
线程395尝试获取读锁
线程396尝试获取读锁
线程367尝试获取读锁
线程424尝试获取读锁
线程335尝试获取读锁
线程392尝试获取读锁
线程364尝试获取读锁
线程370尝试获取读锁
线程424 获取到了读锁...
线程366 获取到了读锁...
线程431 获取到了读锁...
线程336尝试获取读锁
线程384尝试获取读锁
.......
线程433尝试获取读锁
线程421 获取到了读锁...
线程338尝试获取读锁
线程459尝试获取读锁
线程二 获取到了读锁...
线程369 获取到了读锁...
线程330尝试获取读锁
线程392 获取到了读锁...
线程427 获取到了读锁...
线程370 获取到了读锁...
线程372尝试获取读锁
线程320 获取到了读锁...
线程三 获取到了读锁...
线程364 获取到了读锁...
线程426 获取到了读锁...
线程432 获取到了读锁...
........

参考资料

  • 深度解密Java并发工具,精通JUC,成为并发多面手

http://www.ngui.cc/article/show-861317.html

相关文章

2023年深圳CPDA数据分析师认证将于2/25正式开班,快来报名

CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证&#xff0c;它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者&#xff0c;具有广泛的社会认知度和权威性。 无论是地方政府引进人才、公务员报考、各大企业选聘人才&#xff0c;还是招投标加…

2023年关于redis,常常靠常常忘的知识点

一.使用过 Redis 分布式锁么&#xff0c;它是怎么实现的&#xff1f;先拿 setnx 来争抢锁&#xff0c;抢到之后&#xff0c;再用 expire 给锁加一个过期时间防止锁忘记了释放。如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了&#xff0c;那会怎么样&#x…

RDC 2022纪念版开发板-D1S在RT-Smart运行

开发环境 软件 ubuntu20.04VMware Workstation 硬件 RDC2022纪念版开发板全志D1s芯片 材料下载 首先打开虚拟机&#xff0c;创建一个目录存放本次测试的代码&#xff0c;然后克隆RT-Smart用户态代码。 git clone https://github.com/RT-Thread/userapps.git在userapps目…

Navicat Monitor 3.0 现已上市 | 欢迎下载试用

Navicat Monitor 3.0 现已上市Navicat Montior 3.0 现已发布&#xff01;一经发布&#xff0c;受到广大专业运维人员的关注与选择! 五大新亮点带给运维团队最为实用且有效地提升监控能力。其具备 PostgreSQL 服务器监控能力、支持优化慢查询、构建自定义指标、性能分析工具优化…

计算机视觉和图像处理简介:PIL (Python Image Library) 的基本使用

文章大纲 Image Files and PathsLoad Images in PythonPlotting an ImageGrayscale Images, Quantization and Color ChannelsGrayscale ImagesQuantizationColor ChannelsPIL Images into NumPy ArraysIndexingQuestion 1:ReferencesPillow Library (PIL) Estimated time need…

第5章 Linux平台安装MongoDB教程

Linux平台install MongoDB MongoDB 提供了 linux 各个发行版本 64 位的install 包&#xff0c;帅哥可以在官网download install 包。 install 前咱们需要install 各个 Linux 平台依赖包。 Red Hat/CentOS&#xff1a; sudo yum install libcurl opensslUbuntu 18.04 LTS (“…

Qt 开发环境搭建

一、Qt下载与安装 1、qt下载网站https://download.qt.io/ 其中各个目录含义如下&#xff1a; 目录说明snapshots/预览版&#xff0c;最新开发测试的Qt库和开发工具online/在线安装源official_releases/正式发布版&#xff0c;是与开发版相对应的稳定版Qt库和开发工具&#x…

Linux: command: traceroute traceroute6

文章目录 参考昨天遇到一个问题命令功能描述UDP方式ICMP 方式TCP方式traceroute --help解决参考 https://en.wikipedia.org/wiki/Traceroute https://linux.die.net/man/8/traceroute 昨天遇到一个问题 说这个traceroute显示的全是 星花,没有路由信息。根据多年的经验,这种…

MobaXterm使用指南

MobaXterm使用指南 1. 介绍 通俗的来讲&#xff0c;MobaXterm就是一款SSH客户端&#xff0c;它帮助我们在Windows操作系统下去连接并操作Linux服务器。MobaXterm 又名 MobaXVT&#xff0c;是一款增强型终端、X 服务器和 Unix 命令集(GNU/ Cygwin)工具箱。MobaXterm 可以开启多…

【Linux】项目自动化构建工具-make/Makefile与Linux调试器-gdb使用

文章目录Linux项目自动化构建工具-make/MakefileLinux调试器-gdb使用Linux项目自动化构建工具-make/Makefile 背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录…