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

news2025/4/18 23:12:40

文章目录

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

一、前言

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
     */
    @Test
    void 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
     */
    @Test
    void 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.coloradmin.cn/o/197771.html

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

相关文章

OpenCV 图像形态学处理

本文是OpenCV图像视觉入门之路的第11篇文章&#xff0c;本文详细的在图像形态学进行了图像处理&#xff0c;例如&#xff1a;腐蚀操作、膨胀操作、开闭运算、梯度运算、Top Hat Black Hat运算等操作。 OpenCV 图像形态学处理目录 1 腐蚀操作 2 膨胀操作 3 开闭运算 4 梯度运…

57.Isaac教程--定位监视器

定位监视器 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 检测异常系统状态并采取纠正措施有助于确保稳定的系统性能和与预期行为的最小偏差。 为此&#xff0c;Isaac SDK 提供了一个监控框架&#xff0c;可以搭载多种系统观察组件。 该框架目…

达芬奇18.1.2软件更新内容及安装教程(WinMac)

DaVinci Resolve v18.1.2是一款在同一个软件工具中&#xff0c;将剪辑、调色、视觉特效、动态图形和音频后期制作融于一身的解决方案&#xff01;它采用美观新颖的界面设计&#xff0c;易学易用&#xff0c;能让新手用户快速上手操作&#xff0c;还能提供专业人士需要的强大性能…

【论文速递】WACV2023 - 循环相似注意力的小样本医学图像分割

【论文速递】WACV2023 - 循环相似注意力的小样本医学图像分割 【论文原文】&#xff1a;Few-shot Medical Image Segmentation with Cycle-resemblance Attention 获取地址&#xff1a;https://arxiv.org/pdf/2212.03967.pdf博主关键词&#xff1a; 小样本学习&#xff0c;语…

SpringCloud系列(九)[docker 篇] - Centos 7 下 Docker 的安装及基本操作指令

本篇文章将详细介绍 Centos 7 下 Docker 的安装以及一些基本操作指令. DockerDocker 的安装步骤Docker 基本操作指令Docker 的安装步骤 步骤一: 确保自己电脑的虚拟机联网并安装了 yum 工具, 如果没有安装 yum, 则执行下面的命令; yum install -y yum-utils \device-mapper-p…

力扣:多数元素(详解)

前言&#xff1a;本期是关于多数元素的详解&#xff0c;内容包括四大模块&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 今天你c了吗&#xff1f; 题目&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元…

Python爬虫实战之哔哩哔哩二维码登录申请

前言 哈喽&#xff0c;好久不见了吧&#xff0c;各位新年好&#xff01;博主春节也是比较忙的&#xff0c;没时间去写文章和"coding"。最近我们学校也是初九就开学了&#xff0c;所以更加没时间创作了&#x1f923; 言归正传&#xff0c;本次写这篇文章算是想要对我…

软件测试基础(五) 之 了解测试团队的组织架构

今天来了解一下软件测试团队的组织架构模式到底是什么样子。测试团队的组织架构模式的分类一个公司软件测试的组织架构&#xff0c;可能会决定你未来的成长空间&#xff0c;同时也决定了我们的工作模式到底是什么样子。现在测试行业内通常测试团队的组织架构主要分成两种&#…

亚马逊站内流量太少,如何拓展流量渠道增加产品销量?

近两年&#xff0c;经历了行业大洗牌之后&#xff0c;由于入驻平台卖家逐日增多&#xff0c;站内广告成本越来越高&#xff0c;想要抢占更多的站内流量变得愈发困难&#xff0c;一天出不了几单的情况也随处可见。因此&#xff0c;当站内流量的获取遭遇瓶颈&#xff0c;卖家想要…

阿里二面:RocketMQ 消费者拉取一批消息,其中部分消费失败了,偏移量怎样更新?

大家好&#xff0c;我是君哥。最近有读者参加面试时被问了一个问题&#xff0c;如果消费者拉取了一批消息&#xff0c;比如 100 条&#xff0c;第 100 条消息消费成功了&#xff0c;但是第 50 条消费失败&#xff0c;偏移量会怎样更新&#xff1f;就着这个问题&#xff0c;今天…

Pycharm出现‘Error loading package list:Connection refused: connect’问题

问题描述依次打开File->Settting窗口点击图中号弹出如下错误&#xff1a;Package错误窗口‘Error loading package list:Connection refused: connect 一段时间后继续弹出以下窗口&#xff1a;2.问题分析目前这个问题普遍说是由于网络配置原因引起的&#xff0c;在这之前&am…

【数据结构/C++】 树详解

目录树树的定义树的基本术语二叉树⼆叉树的种类满二叉树完全二叉树二叉树的性质二叉树的遍历方法前序遍历中序遍历后序遍历层序遍历二叉树的实现树 树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集。n0时称为空树。在任意一颗非空树中…

[Effective Objective] 块与大中枢派发

为了解决多线程问题&#xff0c;苹果公司以全新的方式设计了多线程。核心就是“块”&#xff08;block&#xff09;与“大中枢派发”&#xff08;Grand Central Dispatch, GCD&#xff09;。 “块”是一种可在C、C及Objective-C代码中使用的“词法闭包”&#xff0c;借由此机制…

在一起多少天怎么设置?如何微信推送在一起多少天

马上情人节要到了&#xff0c;你和你的对象在一起多久了&#xff1f;两个人在恋爱中&#xff0c;会需要记录彼此在一起的每一天&#xff0c;特别是一些重要的纪念日比如100天纪念日&#xff0c;365天、或者520天纪念日。市面上有许多工具&#xff0c;可以帮我们记录这些重要的日…

指针空值nullptr(C++11)

在良好的C/C编程习惯中&#xff0c;声明一个变量时最好给该变量一个合适的初始值&#xff0c;否则可能会出现 不可预料的错误&#xff0c;比如未初始化的指针。如果一个指针没有合法的指向&#xff0c;我们基本都是按照如下 方式对其进行初始化&#xff1a;void TestPtr() { in…

【Docker 02】docker镜像和容器命令大全

对于入门学习者,更推荐的方式是通过官网的Reffrence手册,学习使用命令,不仅存在用法,选项参数的解释,还有用力example。 docker命令的基本语法结构: docker 子命令 [选项] [参数] 一、Docker基本命令 1.镜像有关 一批模板文件,不同的镜像可以包含的环境内容是不一样的,…

深入了解多线程原理

目录 背景知识&#xff1a; 什么是进程&#xff1f; 什么是线程&#xff1f; 线程与进程的区别&#xff1a; Thread类及常用方法&#xff1a; 循环打印的例子&#xff1a; start() 和 run() 的区别&#xff1a; 通过监视窗口查看线程&#xff1a; 创建线程&#xff1a; 1.继承 …

console控制台有sql语句输出但log文件中不输出sql解决方式

控制台可以输出sql&#xff0c;但是log文件中无sql输出&#xff0c;如何解决&#xff1f;把握两点就可以输出&#xff1a;第一点&#xff0c;mybatis 本身的logImpl配置这个参数是配置mybatis所使用的日志框架&#xff0c;取值范围如下&#xff1a;SLF4JLOG4J #表示使用LOG4J作…

提名倒计时! | 2022 龙蜥社区优秀贡献者

各位盆友们&#xff1a;2022 年&#xff0c;那些为龙蜥壮大做出杰出贡献的人们&#xff0c;包括开源背后的推动者、组织者、布道者、代码贡献者&#xff0c;让我们看到了热爱技术的力量&#xff01;为此社区推出「2022 龙蜥社区优秀贡献者」活动。截至目前&#xff0c;距离报名…

CSAPP Malloc Lab

CSAPP Malloc Lab 在这个实验室中&#xff0c;您将为C程序编写一个动态存储分配器&#xff0c;即您自己版本的malloc、free和realloc例程&#xff0c;实现一个正确&#xff0c;高效和快速的分配器。本实验性能指标有两个方面&#xff0c;内存利用率和吞吐量&#xff0c;这两个…