java 读写锁_Java中的读写锁「建议收藏」

java 读写锁_Java中的读写锁「建议收藏」一、读写锁1、初识读写锁a)Java中的锁——Lock和synchronized中介绍的ReentrantLock和synchronized基本上都是排它锁,意味着这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,在写线程访问的时候其他的读线程和写线程都会被阻塞。读写锁维护一对锁(读锁和写锁),通过锁的分离,使得并发性提高。b)关于读写锁的基本使用:在不使用读写锁的…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、读写锁

1、初识读写锁

a)Java中的锁——Lock和synchronized中介绍的ReentrantLock和synchronized基本上都是排它锁,意味着这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,在写线程访问的时候其他的读线程和写线程都会被阻塞。读写锁维护一对锁(读锁和写锁),通过锁的分离,使得并发性提高。

b)关于读写锁的基本使用:在不使用读写锁的时候,一般情况下我们需要使用synchronized搭配等待通知机制完成并发控制(写操作开始的时候,所有晚于写操作的读操作都会进入等待状态),只有写操作完成并通知后才会将等待的线程唤醒继续执行。

如果改用读写锁实现,只需要在读操作的时候获取读锁,写操作的时候获取写锁。当写锁被获取到的时候,后续操作(读写)都会被阻塞,只有在写锁释放之后才会执行后续操作。并发包中对ReadWriteLock接口的实现类是ReentrantReadWriteLock,这个实现类具有下面三个特点

①具有与ReentrantLock类似的公平锁和非公平锁的实现:默认的支持非公平锁,对于二者而言,非公平锁的吞吐量由于公平锁;

②支持重入:读线程获取读锁之后能够再次获取读锁,写线程获取写锁之后能再次获取写锁,也可以获取读锁。

③锁能降级:遵循获取写锁、获取读锁在释放写锁的顺序,即写锁能够降级为读锁

2、读写锁源码分析

a)ReadWriteLock接口中只有两个方法,分别是readLock和writeLock

1 public interfaceReadWriteLock {2 /**

3 * 返回读锁4 */

5 Lock readLock();6

7 /**

8 * 返回写锁9 */

10 Lock writeLock();11 }

b)关于读写读写状态的设计

①作为已经实现的同步组件,读写锁同样是需要实现同步器来实现同步功能,同步器的同步状态就是读写锁的读写状态,只是读写锁的同步器需要在同步状态上维护多个读线程和写线程的状态。使用按位切割的方式将一个整形变量按照高低16位切割成两个部分。对比下图,低位值表示当前获取写锁的线程重入两次,高位的值表示当前获取读锁的线程重入一次。读写锁的获取伴随着读写状态值的更新。当低位为0000_0000_0000_0000的时候表示写锁已经释放,当高位为0000_0000_0000_0000的时候表示读锁已经释放。

②从下面的划分得到:当state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取;如果写状态不等于0的话,读锁没有被获取。这个特点也在源码中实现。

39ddef54a9b11e5b947e70a1c09924c8.png

c)写锁writeLock

①上面说到过,读写锁是支持重入的锁,而对于写锁而言还是排他的,这样避免多个线程同时去修改临界资源导致程序出现错误。如果当前线程已经获取了写锁,则按照上面读写状态的设计增加写锁状态的值;如果当前线程在获取写锁的时候,读锁已经被获取或者该线程之前已经有别的线程获取到写锁,当前线程就会进入等待状态。

1 static final int SHARED_SHIFT = 16;2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) – 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111

3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值

4 protected final boolean tryAcquire(intacquires) {5 /*

6 * Walkthrough:7 * 1. 如果读状态不为0或者写状态不为0并且写线程不是自己,返回false8 * 2. 如果已经超过了可重入的计数值MAX_COUNT,就会返回false9 * 3. 如果该线程是可重入获取或队列策略允许,则该线程有资格获得锁定;同时更新所有者和写锁状态值10 */

11 Thread current = Thread.currentThread(); //获取当前线程

12 int c = getState(); //获取当前写锁状态值

13 int w = exclusiveCount(c); //获取写状态的值14 //当同步状态state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取

15 if (c != 0) {16 if (w == 0 || current !=getExclusiveOwnerThread())17 return false;18 if (w + exclusiveCount(acquires) > MAX_COUNT) //如果已经超过了可重入的计数值MAX_COUNT,就会返回false

19 throw new Error(“Maximum lock count exceeded”);20 //重入锁:更新状态值

21 setState(c +acquires);22 return true;23 }24 if (writerShouldBlock() ||

25 !compareAndSetState(c, c +acquires))26 return false;27 setExclusiveOwnerThread(current);28 return true;29 }

②分析一下上面的写锁获取源码

tryAcquire中线程获取写锁的条件:读锁没有线程获取,写锁被获取并且被获取的线程是自己,那么该线程可以重入的获取锁,而判断读锁是否被获取的条件就是(当同步状态state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取)。对于读写锁而言,需要保证写锁的更新结果操作对读操作是可见的,这样的话写锁的获取就需要保证其他的读线程没有获取到读锁。

③写锁的释放源码

写锁的释放和ReentrantLock的锁释放思路基本相同,从源码中可以看出来,每次释放都是减少写状态,直到写状态值为0(exclusiveCount(nextc) == 0)的时候释放写锁,后续阻塞等待的读写线程可以继续竞争锁。

1 static final int SHARED_SHIFT = 16;2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) – 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111

3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值

4 protected final boolean tryRelease(intreleases) {5 if (!isHeldExclusively())6 throw newIllegalMonitorStateException();7 int nextc = getState() -releases;8 boolean free = exclusiveCount(nextc) == 0; //写状态值为0,就释放写锁,并将同步状态的线程持有者置为null,然后更新状态值

9 if(free)10 setExclusiveOwnerThread(null);11 setState(nextc);12 returnfree;13 }

d)读锁readLock

①读锁是同样是支持重入的,除此之外也是共享式的,能够被多个线程获取。在同一时刻的竞争队列中,如果没有写线程想要获取读写锁,那么读锁总会被读线程获取到(然后更新读状态的值)。每个读线程都可以重入的获取读锁,而对应的获取次数保存在本地线程中,由线程自身维护该值。

②获取读锁的条件:其他线程已经获取了写锁,则当前线程获取读锁会失败而进入等待状态;如果当前线程获取了写锁或者写锁没有被获取,那么就可以获取到读锁,并更细同步状态(读状态值)。

③读锁的每次释放都是减少读状态,

f)锁的降级

锁降级的概念:如果当先线程是写锁的持有者,并保持获得写锁的状态,同时又获取到读锁,然后释放写锁的过程。(注意不同于这样的分段过程:当前线程拥有写锁,释放掉写锁之后再获取读锁的过程,这种分段过程不能称为锁降级)。

1 classCachedData {2 Object data;3 volatile booleancacheValid;4 final ReentrantReadWriteLock rwl = newReentrantReadWriteLock();5

6 voidprocessCachedData() {7 //获取读锁

8 rwl.readLock().lock();9 if (!cacheValid) {10 //在获取写锁之前必须释放读锁,不释放的话下面写锁会获取不成功,造成死锁

11 rwl.readLock().unlock();12 //获取写锁

13 rwl.writeLock().lock();14 try{15 //重新检查state,因为在获取写锁之前其他线程可能已经获取写锁并且更改了state

16 if (!cacheValid) {17 data =…18 cacheValid = true;19 }20 //通过在释放写锁定之前获取读锁定来降级21 //这里再次获取读锁,如果不获取,那么当写锁释放后可能其他写线程再次获得写锁,导致下方`use(data)`时出现不一致的现象22 //这个操作就是降级

23 rwl.readLock().lock();24 } finally{25 rwl.writeLock().unlock(); //释放写锁,由于在释放之前读锁已经被获取,所以现在是读锁获取状态

26 }27 }28

29 try{30 //使用完后释放读锁

31 use(data);32 } finally{33 rwl.readLock().unlock(); //释放读锁

34 }35 }36 }}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/169977.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 罗技k375s键盘怎么连接_罗技k380键盘怎么连接手机

    罗技k375s键盘怎么连接_罗技k380键盘怎么连接手机罗技K375s无线蓝牙键盘使用总结(连接|手感)首先我们先来试试优联连接。优联只支持PC(废话)。如果之前没有配对过,需要再下个优联的软件来进行配对。不得不说,罗技的网站打开真慢,服务器估计在国外。而且中文主页,K375S竟然还没上架,列表里没有,也搜索不到。我用的是旧的优联,已经配对了M570。配对新设备之后,其实就连上了。要你输入一些字符确认。不过这里的K370S是怎么回事,这命名……既然…

    2022年10月15日
    4
  • 各位学弟学妹,别再看教材了,时间复杂度看这篇就好了[通俗易懂]

    各位学弟学妹,别再看教材了,时间复杂度看这篇就好了[通俗易懂]时间复杂度是学习算法的基石,今天我们来聊聊为什么要引入时间复杂度,什么是时间复杂度以及如何去算一个算法的时间复杂度一、刻画算法的运行时间某日,慧能叫来了一尘打算给他补习补习一下基础知识,只见克写了一段非常简单的代码一尘看老师有点生气,开始虚心请教了为了方便讨论,这里我们把每一条语句的执行时间都看做是一样的,记为一个时间单元①蓝色框的两条语句,花费两个时间单元②黑色框的一条语句,花费n+1个时间单元③红色框的两条语句,花费2*n个时间单元这不是.

    2022年5月15日
    34
  • Java中的Scanner(详解常见用法)

    Java中的Scanner(详解常见用法)OJ中遇到的问题和例子:doublenextDouble(),floatnextFloat(),intnextInt()等与nextLine()连用时都存在遗留的换行符影响nextLine()读取输入的问题,解决的办法是:在每一个next()、nextDouble()、nextFloat()、nextInt()等语句之后加一个nextLine()语句,将被next()去掉的…

    2022年7月8日
    23
  • 用python来开发webgame服务端(1)[通俗易懂]

    用python来开发webgame服务端(1)[通俗易懂]http://ciniao.me/article.php?id=9 刺鸟原创文章,转载请注明出处    在开始之前,先简单描述一下项目的特点:我要实现的是一个mmorpg的webgame,地图上需要看到其他的玩家,战斗系统采用半回合制的模式,所谓的半回合制,即是:采用回合制的画面布局,友方和敌方分列左右,但是战斗的中途,其他的玩家可以及时的随时插入这场战斗。当然,作为一款rpgGa

    2022年5月30日
    53
  • IAAS云平台搭建详细步骤(云平台openstack)

    先电云iaas(openstack)搭建(一)这里我们首先进行基本环境的配置和安装。由于整个iaas完整安装过程内容过多,我这里将分步进行为了方便操作和更直观的观察我这里包括后续步骤主要使用图片进行操作密码设置000000输入法设置为英文所需环境:virtualbox6.0centos-1511.isoxiandian-iaas.iso…

    2022年4月9日
    77
  • docker镜像操作_docker导出容器为镜像

    docker镜像操作_docker导出容器为镜像零.docker常用命令#镜像名版本标签镜像id创建时间镜像大小REPOSITORYTAGIMAGEIDCREATEDSIZEhello-worldlatestfce289

    2022年8月16日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号