这里写目录标题

  • 【< J.U.C>】
    • [1] AQS
    • [2] ReentrantLock
    • [3] ReentrantReadWriteLock
    • [4] StampedLock
    • [5] Semaphore
    • [6] threadlocal
    • [7] CountDownLatch
    • [8] CyclicBarrier?
    • [9] Atmoic
    • [10] FutureTask
    • [11] ForkJoin

【< J.U.C>】

[1] AQS

见专题

[2] ReentrantLock

见专题

[3] ReentrantReadWriteLock

https://www.cnblogs.com/xiaoxi/p/9140541.html

现实中有这样一种场景:**对共享资源有读和写的操作,且写操作没有读操作那么频繁。**在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁(ReadLock和WriteLock),一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:

  • 线程进入读锁(共享)的前提条件:

没有其他线程的写锁,

没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。

  • 线程进入写锁(互斥)的前提条件:

没有其他线程的读锁

没有其他线程的写锁

  • 而读写锁有以下六个重要的特性:

(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。

(2)重进入:读锁和写锁都支持线程重进入。

(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁

(4)不支持锁升级:为了保证内存可见性

(5) 不支持conditon函数

(6) 读写锁允许多个读线程访问,但是写线程时,不允许其他线程。

1. 读写状态的设计

如何用一个state值来表示读写两种状态呢?

同步状态在重入锁的实现中是表示被同一个线程重复获取的次数,即一个整型变量来维护,但是之前的那个表示仅仅表示是否锁定,而不用区分是读锁还是写锁。而读写锁需要在同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。

读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。

假设当前同步状态值为S,get和set的操作如下:

(1)获取写状态:

S&0x0000FFFF:将高16位全部抹去

(2)获取读状态:

S>>>16:无符号补0,右移16位

(3)写状态加1:

S+1

(4)读状态加1:

S+(1<<16)即S + 0x00010000

在代码层的判断中,如果S不等于0,当写状态(S&0x0000FFFF),而读状态(S>>>16)大于0,则表示该读写锁的读锁已被获取。

4、写锁的获取与释放

看下WriteLock类中的lock和unlock方法:

public void lock() {sync.acquire(1);
}public void unlock() {sync.release(1);
}

可以看到就是调用的独占式同步状态的获取与释放,因此真实的实现就是Sync的 tryAcquire和 tryRelease。

写锁的获取,看下tryAcquire:

 1 protected final boolean tryAcquire(int acquires) {2     //当前线程3     Thread current = Thread.currentThread();4     //获取状态5     int c = getState();6     //写线程数量(即获取独占锁的重入数)7     int w = exclusiveCount(c);8     9     //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
10     if (c != 0) {
11         // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;
12         // 如果写锁状态不为0且写锁没有被当前线程持有返回false
13         if (w == 0 || current != getExclusiveOwnerThread())
14             return false;
15
16         //判断同一线程获取写锁是否超过最大次数(65535),支持可重入
17         if (w + exclusiveCount(acquires) > MAX_COUNT)
18             throw new Error("Maximum lock count exceeded");
19         //更新状态
20         //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。
21         setState(c + acquires);
22         return true;
23     }
24
25     //到这里说明此时c=0,读锁和写锁都没有被获取
26     //writerShouldBlock表示是否阻塞
27     if (writerShouldBlock() ||
28         !compareAndSetState(c, c + acquires))
29         return false;
30
31     //设置锁为当前线程所有
32     setExclusiveOwnerThread(current);
33     return true;
34 }

其中exclusiveCount方法表示占有写锁的线程数量,源码如下:

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

说明:直接将状态state和(2^16 - 1)做与运算,其等效于将state模上2^16。写锁数量由state的低十六位表示。

从源代码可以看出,获取写锁的步骤如下:

(1)首先获取c、w。c表示当前锁状态;w表示写线程数量。然后判断同步状态state是否为0。如果state!=0,说明已经有其他线程获取了读锁或写锁,执行(2);否则执行(5)。

(2)如果锁状态不为零(c != 0),而写锁的状态为0(w = 0),说明读锁此时被其他线程占用,所以当前线程不能获取写锁,自然返回false。或者锁状态不为零,而写锁的状态也不为0,但是获取写锁的线程不是当前线程,则当前线程也不能获取写锁。

(3)判断当前线程获取写锁是否超过最大次数,若超过,抛异常,反之更新同步状态(此时当前线程已获取写锁,更新是线程安全的),返回true。

(4)如果state为0,此时读锁或写锁都没有被获取,判断是否需要阻塞(公平和非公平方式实现不同),在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),如果不需要阻塞,则CAS更新同步状态,若CAS成功则返回true,失败则说明锁被别的线程抢去了,返回false。如果需要阻塞则也返回false。

(5)成功获取写锁后,将当前线程设置为占有写锁的线程,返回true。

方法流程图如下:

写锁的释放,tryRelease方法:

[](javascript:void(0)

【< J.U.C>】相关推荐

  1. 【SPRS J P RS 2022】小目标检测模块:A Normalized Gaussian Wasserstein Distance for Tiny Object Detection

    [SPRS J P & RS 2022]A Normalized Gaussian Wasserstein Distance for Tiny Object Detection A Norma ...

  2. Python(分治算法)问题 F: 求逆序对_给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

    问题 F: 求逆序对 题目描述 给定一个序列a1,a2,-,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目. 注意:n<=10^5,ai<=10^5 ...

  3. <From Zero to Hero>零基础学习Python基础语法【条件判断与条件嵌套】

    目录 条件判断 单向判断:if 双向判断:if-else- 多向判断:if-elif-else- if嵌套 if嵌套的执行顺序 如何写嵌套代码 小练习1 小练习2 对于Python来讲,需要正确的[沟 ...

  4. java加载c库阻塞_【死磕Java並發】-----J.U.C之阻塞隊列:DelayQueue

    DelayQueue是一個支持延時獲取元素的無界阻塞隊列.里面的元素全部都是"可延期"的元素,列頭的元素是最先"到期"的元素,如果隊列里面沒有元素到期,是不能從 ...

  5. 【云原生微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式

    文章目录 一.前言 二.Gateway集成Sentinel API 0.集成Sentinel的核心概念 1)GatewayFlowRule 和 ApiDefinition 2)GatewayFlowR ...

  6. NLP-词向量-发展:词袋模型【onehot、tf-idf】 -> 主题模型【LSA、LDA】 -> 词向量静态表征【Word2vec、GloVe、FastText】 -> 词向量动态表征【Bert】

    NLP-词向量-发展: 词袋模型[onehot.tf-idf] 主题模型[LSA.LDA] 基于词向量的静态表征[Word2vec.GloVe.FastText] 基于词向量的动态表征[Bert] 一 ...

  7. SWPU-DS)若有 n 阶对称矩阵 A,以行序为主序方式,将其下三角形的元素(包括主对角线上所有元素)依次存放于一维数组B[1..(n(n+1))/2]中,则在 B 中确定 a[i, j](i<j)

    SWPU-DS)若有 n 阶对称矩阵 A,以行序为主序方式,将其下三角形的元素(包括主对角线上所有元素)依次存放于一维数组B[1-(n(n+1))/2]中,则在 B 中确定 a[i, j](i< ...

  8. java核心技术 基础知识<集合并发part1>

    文章目录 java核心技术 基础知识<集合&并发part1> 9 泛型程序设计 9.5 算法 9.6 遗留的集合 14 并发 14.2 中断线程 14.3 线程状态 14.4 线程 ...

  9. 【一些好听的英文歌曲】

    原文地址:[一些好听的英文歌曲]作者:姑娘我笑着活 1.Bubbly--Colbie Caillat(你听过一遍就会非常喜欢的歌): 2.Burning--Maria Arredondo: 3.Hap ...

最新文章

  1. 图解Oracle同义词
  2. java字节码运行原理_JVM 内部原理(六)— Java 字节码基础之一
  3. 电脑常见的VGA、DVI、PS/2、USB等接口知识笔记,值得收藏!
  4. [LCT动态树] [NOI2014]魔法森林,[ZJOI2018]历史
  5. ssl2346-联络员【图论,最小生成树】
  6. 阿里云贾扬清:数据湖正成为企业数据应用创新标配
  7. (转)Spring4.2.5+Hibernate4.3.11组合开发
  8. 【OpenCV入门指南】第八篇 灰度直方图
  9. BFGS优化算法简介
  10. 哈夫曼树及哈夫曼编码例题
  11. vmware fusion 7 序列号
  12. 【论文阅读】DNS隧道攻击检测算法整合
  13. 我们如何学习:学会学习再学习
  14. android打电话的intent,如何在Android中使用intent打电话?
  15. ubuntu设置相机为固定焦距
  16. hsk内网穿透+SERV-U+搭建FTP服务器+并实现外网访问
  17. python图像锐化 增强边缘_[Python图像处理]十一.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子,Schar算子...
  18. Linux磁盘挂载问题 ***is apparently in use by the system; will not make a filesystem here!
  19. 浅谈代码规范基础调试几道面试题
  20. 组态王与西门子S7 200 SMART连接

热门文章

  1. 彻底解决 webpack 打包文件体积过大
  2. 2018保研夏令营调研笔记
  3. Windows IDEA 字体美化
  4. linux 笔记本电脑_符合Linux的笔记本电脑供应商和产品
  5. 【Python】【setFocus】焦点
  6. centos6 trouble shooting
  7. Netty框架初步学习
  8. Cisco NSF 与NSR 与GR
  9. wps word修改目录行间距后出现空行的现象,且删除按键无效
  10. Docker学习笔记 (狂神说)