浅谈AQS锁实现机制(含ReentrantReadWriteLock读写锁加锁解锁相关源码分析)
Posted 默辨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈AQS锁实现机制(含ReentrantReadWriteLock读写锁加锁解锁相关源码分析)相关的知识,希望对你有一定的参考价值。
一、分析AQS锁
在Java语言层面,它拥有自己的锁实现机制(JVM层面的是synchronized)。这个锁机制就是AbstractQueuedSynchronizer类,也就是我们常说的AQS。我们常见的ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier等,都是基于AQS来实现的,只是不同的并发工具类使用到的是AQS的不同特性,如ReentrantLock加锁操作使用了互斥特性,Semaphore信号量就是利用了共享的特性,但两者都是在state变量上做文章。
AQS是一个抽象类,他无法直接实例化为一个对象,他只能依附于其他工具类的实现。AQS使用了一个模板方法的设计模式,它给出了我们实现锁的规范,我们只需要按照它的规范来,就可以完成对应的加锁、解锁逻辑。
不懂模板方法设计模式的可以参考我之前的博客:模板方法模式
本文仅讨论尝试加锁(tryAcquire)和尝试解锁(tryRelease)这两个方法。我认为这是比较核心的两个方法。
为什么我说这两个方法是比较核心的方法呢?我们以ReentrantLock为例,调用lock方法为起点。更详细的过程及源码分析,请参考浅谈AQS同步队列(含ReentrantLock加锁和解锁源码分析)此处仅进行流程分析。
1、选择调用ReentrantLock中的公平锁还是非公平锁实现(在创建ReentrantLock对象的时候,就会初始化出我们的锁对象,即可以指定是公平还是非公平的锁),我们以默认的非公平锁为例
2、然后调用当前类的acquire,而该类是AQS中实现的,即会调用父类方法
3、此时会调用对应的tryAcquire方法尝试获取锁
4、该方法在AQS中是一个会报错的实现,即具体实现方法只能是具体子类
5、此时就根据ReentrantLock中的方法逻辑进行获取锁
6、解锁流程逻辑同理
总结:AQS给出了锁的实现规则,具体的锁工具类,只需要实现对应的方法即可完成一把锁的功能
二、自定义一把AQS锁
总结上面的关键点:
1、lock方法为我们加锁的入口
2、tryAcquire为我们获取锁的逻辑
于是我们就可以自定义一把锁,完成锁的基本功能,代码如下:
public class MobianLock extends AbstractQueuedSynchronizer
// 用于加锁的入口
public void lock()
acquire(1);
// 用于解锁的入口
public void unlock()
release(1);
@Override
protected boolean tryAcquire(int arg)
// cas加锁 成功返回true
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
return true;
return false;
@Override
protected boolean tryRelease(int arg)
// 释放锁逻辑
setExclusiveOwnerThread(null);
setState(0);
return true;
测试结果:
锁效果成功
三、分析ReentrantReadWriteLock锁源码
ReentrantReadWriteLock锁的使用不再讲解
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁(读多写少)。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源(读读可以并发);但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了(读写,写读,写写互斥)。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它内部,维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁,描述如下:
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
而读写锁有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 可重入:读锁和写锁都支持线程重入。以读写线程为例:读线程获取读锁后,能够再次获取读锁。写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁。
- 锁降级:遵循获取写锁、再获取读锁最后释放写锁的次序,写锁能够降级成为读锁。
ReentrantReadWriteLock的类图结构
根据ReentrantReadWriteLock类的情况可以两部分
- WriteLock和ReadLock为一部分:关键的读写锁逻辑
- Sync为一部分:维护锁的状态信息
1、Sync
直接继承AQS,在AQS的基础上,添加了一些要素
这里涉及了很多的位运算,其原因都要归咎于在ReentrantReadWriteLock锁的内部,它是将一个int类型的state分成16高位+16低位来处理的。
这个状态数字的判断,在后面的读写锁加锁过程中会经常使用到。
总结:高16位为读锁状态标志位,低16位为写锁状态标志位
2、ReadLock
读锁部分,依然可以分为lock和unlock方法,它们分别调用的是acquireShared和releaseShared方法。想都不用想,最终肯定是调用到tryAcquireShared和tryReleaseShared方法
public void lock()
sync.acquireShared(1);
public void unlock()
sync.releaseShared(1);
加锁逻辑
解锁逻辑
3、WriteLock
同理,会调用到tryAcquire和tryRelease方法
public void lock()
sync.acquire(1);
public void unlock()
sync.release(1);
加锁逻辑
解锁逻辑
以上是关于浅谈AQS锁实现机制(含ReentrantReadWriteLock读写锁加锁解锁相关源码分析)的主要内容,如果未能解决你的问题,请参考以下文章