AQS之ReentrantReadWriteLock写锁
- 用法
1.1 定义一个安全的list集合
public class LockDemo {
ArrayList<Integer> arrayList = new ArrayList<>();//定义一个集合
// 定义读锁
ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock(true).readLock();
// 定义写锁
ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock(true).writeLock();
public void addEle(Integer ele) {
writeLock.lock(); // 获取写锁
arrayList.add(ele);
writeLock.unlock(); // 释放写锁
}
public Integer getEle(Integer index) {
try{
readLock.lock(); // 获取读锁
Integer res = arrayList.get(index);
return res;
} finally{
readLock.unlock();// 释放读锁
}
}
}
1.2 Sync 源码中的属性与方法在上一篇文章中已经讲过了
- 获取写锁源码分析
ReentrantReadWriteLock中的lock方法
public void lock() {
sync.acquire(1);
}
AbstractQueuedSynchronizer中的acquire方法
public final void acquire(int arg) {
// 获取锁失败则进入阻塞队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))****,中的acquireQueued方法和addWaiter方法在前面的文章中都已经进行了详细的解释说明。

ReentrantReadWriteLock中的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取状态
int c = getState();
// 计算写线程数量就是独占锁的可从入数量
int w = exclusiveCount(c);
// 当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
if (c != 0) {
// 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;
// 如果写锁状态不为0且写锁没有被当前线程持有返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 判断同一线程获取写锁是否超过最大次数(65535),支持可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新状态
//此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可
setState(c + acquires);
return true;
}
//到这里说明此时c=0,读锁和写锁都没有被获取
//writerShouldBlock表示是否阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置锁为当前线程所有
setExclusiveOwnerThread(current);
return true;
}
static final class FairSync extends Sync {
// 写锁是否应该被阻塞
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
}
- 获取写锁流程图
3.1 流程图获取写锁过程

3.2 流程图获取写锁过程解析
写锁的获取过程如下:
- 首先获取c、w。c表示当前锁状态;w表示写线程数量。然后判断同步状态state是否为0。如果state!=0,说明已经有其他线程获取了读锁或写锁。
- 如果锁状态不为零(c != 0),而写锁的状态为0(w = 0),说明读锁此时被其他线程占用,所以当前线程不能获取写锁,自然返回false。或者锁状态不为零,而写锁的状态也不为0,但是获取写锁的线程不是当前线程,则当前线程也不能获取写锁。
- 判断当前线程获取写锁是否超过最大次数,若超过,抛异常,反之更新同步状态(此时当前线程已获取写锁,更新是线程安全的),返回true。
- 如果state为0,此时读锁或写锁都没有被获取,判断是否需要阻塞(公平和非公平方式实现不同),在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),如果不需要阻塞,则CAS更新同步状态,若CAS成功则返回true,失败则说明锁被别的线程抢去了,返回false。如果需要阻塞则也返回false。
- 成功获取写锁后,将当前线程设置为占有写锁的线程,返回true。
- 获取锁失败的话,将当前线程进行放入阻塞队列中。
- 释放写锁源码分析
ReentrantReadWriteLock中的unlock方法
public void unlock() {
sync.release(1);
}
AbstractQueuedSynchronizer中的release方法
public final boolean release(int arg) {
// 如果返回true 那么释放成功了
if (tryRelease(arg)) {
Node h = head;
// 如果头部不为空,并且头节点的waitStatus是唤醒状态那么唤醒后继线程
if (h != null && h.waitStatus != 0)
// 唤醒后继线程
unparkSuccessor(h);
return true;
}
return false;
}
ReentrantReadWriteLock中tryRelease方法
protected final boolean tryRelease(int releases) {
// 若锁的持有者不是当前线程,抛出异常
if (!isHeldExclusively())
// 非法的监控器异常
throw new IllegalMonitorStateException();
// 计算写锁的新线程数
int nextc = getState() - releases;
// 如果独占模式重入数为0了,说明独占模式被释放
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 设置独占线程为空
setExclusiveOwnerThread(null);
// 设置写锁的新线程数
// 不管独占模式是否被释放,更新独占重入数
setState(nextc);
return free;
}
protected final boolean isHeldExclusively() {
// 若当前线程是当前锁的持有线程那么返回true
return getExclusiveOwnerThread() == Thread.currentThread();
}
- 释放写锁流程图
5.1 流程图释放过程

5.2 流程图释放过程解析
写锁的释放过程:
- 首先查看当前线程是否为写锁的持有者,如果不是抛出异常。然后检查释放后写锁的线程数是否为0,如果为0则表示写锁空闲了,释放锁资源将锁的持有线程设置为null,否则释放仅仅只是一次重入锁而已,并不能将写锁的线程清空。
- 说明:此方法用于释放写锁资源,首先会判断该线程是否为独占线程,若不为独占线程,则抛出异常,否则,计算释放资源后的写锁的数量,若为0,表示成功释放,资源不将被占用,否则,表示资源还被占用。
- 总结
6.1 state 解析
private volatile int state;
int 类型占有 4个字节一个字节8位,所以 state 一个 32 位,高 16 位 代表读锁 低 16 位代表 写锁。
// 0x0000FFFF 16 进制
// 1111111111111111 2 进制
// 65535 10 进制
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 65536
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //65535
// 1111111111111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535
// 1111111111111111
如果此时同步状态位 c 那么获取写状态 c & EXCLUSIVE_MASK
如果此时同步状态位 c 那么获取读状态 c >>>16 无符号补0,右移16位
6.2 注意
以上便是ReentrantReadWriteLock中写锁的分析,下一篇文章将是Condition的分析,如有错误之处,帮忙指出及时更正,谢谢,如果喜欢谢谢点赞加收藏加转发(转发注明出处谢谢!!!)
AQS之ReentrantReadWriteLock写锁的更多相关文章
- AQS之ReentrantReadWriteLock精讲分析上篇
1.用法 1.1 定义一个安全的list集合 public class LockDemo { ArrayList<Integer> arrayList = new ArrayList< ...
- 沉淀再出发:关于java中的AQS理解
沉淀再出发:关于java中的AQS理解 一.前言 在java中有很多锁结构都继承自AQS(AbstractQueuedSynchronizer)这个抽象类如果我们仔细了解可以发现AQS的作用是非常大的 ...
- Java并发(8)- 读写锁中的性能之王:StampedLock
在上一篇<你真的懂ReentrantReadWriteLock吗?>中我给大家留了一个引子,一个更高效同时可以避免写饥饿的读写锁---StampedLock.StampedLock实现了不 ...
- 一文搞懂AQS及其组件的核心原理
@ 目录 前言 AbstractQueuedSynchronizer Lock ReentrantLock 加锁 非公平锁/公平锁 lock tryAcquire addWaiter acquireQ ...
- ReentrantReadWriteLock源码
@SuppressWarnings("restriction") public class ReentrantReadWriteLock1 implements ReadWrite ...
- ReentrantReadWriteLock源码分析
代码在后面 读锁 = 共享锁 读锁写锁,公用一个Sync AQS state. 写锁是排他的,看到有人获取锁,他不会去获取,他获取了锁,别人也不会进来获取锁. 写锁的获取跟ReentarntLock一 ...
- 源码分析:同步基础框架——AbstractQueuedSynchronizer(AQS)
简介 AQS 全称是 AbstractQueuedSynchronizer,位于java.util.concurrent.locks 包下面,AQS 提供了一个基于FIFO的队列和维护了一个状态sta ...
- Lock锁子类了解一下
前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 Java锁机制了解一下 AQS简简单单过一遍 只有光头才能变强! 上一篇已经将Loc ...
- Java并发编程原理与实战三十九:JDK8新增锁StampedLock详解
1.StampedLock是做什么的? ----->它是ReentrantReadWriteLock 的增强版,是为了解决ReentrantReadWriteLock的一些不足. 2.Ree ...
随机推荐
- Java CPU占用过高之诊断
Redhat, java 先用top 命令(默认为cpu占用排序), 确认java占用CPU kill -3 <java pid> ps Hh -eo pid,tid,pcpu | sor ...
- Web服务器-并发服务器-Epoll(3.4.5)
@ 目录 1.介绍 2.代码 关于作者 1.介绍 epoll是一种解决方案,nginx就是用的这个 中心思想:不要再使用多进程,多线程了,使用单进程,单线程去实现并发 在上面博客实现的代码中使用过的轮 ...
- 面试级解析HashMap
------------恢复内容开始------------ 在介绍HashMap之前,有必要先给大家介绍一些参数的概念 HashMap的最大容量,capacity译为容量,capacity就是指Ha ...
- wildfly 21的domain配置
目录 简介 wildfly模式简介 domain controller的配置 Host controller的配置文件 忽略域范围的资源 Server groups Servers 总结 简介 wil ...
- Python批量创建word文档(1)- 纯文字
Python创建word文档,任务要求:小杨在一家公司上班,每天都需要给不同的客户发送word文档,以告知客户每日黄金价格.最后贴上自己的联系方式.代码如下: 1 ''' 2 #python根据需求新 ...
- HCIP----静态实验
要求: 1.全网可达 2.拓扑中所需地址全部基于192.168.0.0/24划分所得 3.使用静态路由 4.R1有三个环回,需要汇总 5.PC1与PC3属于同一VLAN,PC2和PC4属于同一VLAN ...
- js中Tabs插件打开的标签页过多自动关闭
js方法 function addTab(ResourceID, ResourceName, Url) { if (Url != "" && Url != null ...
- Spring Boot 中使用 Quartz 实现任务调度
Quartz 概述 Quartz 是 OpenSymphony 开源组织在 Job Scheduling 领域又一个开源项目,它可以与 J2EE. J2SE 应用程序相结合也可以单独使用.Quartz ...
- jdbc编程学习(1)
一,背景 1996年,sun公司发布了第一版本的数据库连接(JDBC)API,使编程人员可以通过这个API接口连接到数据库,并使用结构化查询语言(SQL)完成对数据库的查找与更新.JDBC自此成为Ja ...
- 转【深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接】
转自:https://www.jb51.net/article/39432.htm 1.内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符).包括相等联接和自然联接. ...