提示:本文需要结合ReadWriteLock和ReentrantLock的原理看会比较清晰
前言
提示:这里可以添加本文要记录的大概内容:将自己手写的ReadWriteLock和ReentrantLock根据模板方法模式抽象,最后变成AQS
一、AQS是什么?
AQS叫做抽象队列同步器。普通的队列,只是资源的片段,线程并不会阻塞;让线程排队,就需要这些公共的业务逻辑,抽出来了作为模板,这部分就叫做AQS
二、抽象步骤
1.将ReadWriteLock和ReentrantLock代码对比
对比代码可以知道,ReadWriteLock和ReentrantLock的lock()和unlock()、lockShared()和unLockShared()是不变的,所以可以抽取出来,然后将两个代码中的变量也抽取出来,再将代码中不是公共逻辑的方法进行留白(模板方法模式)
代码如下(示例):
public class BingAQS {
AtomicInteger readCount = new AtomicInteger(0);
AtomicInteger writeCount = new AtomicInteger(0);
//独占锁 拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列 在JDK,队列是用链表实现的,但是作用是一样的
public volatile LinkedBlockingQueue<BingAQS.WaitNode> waiters = new LinkedBlockingQueue<BingAQS.WaitNode>();
class WaitNode{
int type = 0; //0 为想获取独占锁的线程, 1为想获取共享锁的线程
Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int arg){
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
//获取独占锁
public void lock() {
int arg = 1;
//尝试获取独占锁,若成功,退出方法, 若失败...
if (!tryLock(arg)){
//标记为独占锁
BingAQS.WaitNode waitNode = new BingAQS.WaitNode(Thread.currentThread(), 0, arg);
waiters.offer(waitNode); //进入等待队列
//循环尝试拿锁
for(;;){
//若队列头部是当前线程
BingAQS.WaitNode head = waiters.peek();
if (head!=null && head.thread == Thread.currentThread()){
if (!tryLock(arg)){ //再次尝试获取 独占锁
LockSupport.park(); //若失败,挂起线程
} else{ //若成功获取
waiters.poll(); // 将当前线程从队列头部移除
return; //并退出方法
}
}else{ //若不是队列头部元素
LockSupport.park(); //将当前线程挂起
}
}
}
}
//释放独占锁
public boolean unlock() {
int arg = 1;
//尝试释放独占锁 若失败返回true,若失败...
if(tryUnlock(arg)){
BingAQS.WaitNode next = waiters.peek(); //取出队列头部的元素
if (next !=null){
Thread th = next.thread;
LockSupport.unpark(th); //唤醒队列头部的线程
}
return true; //返回true
}
return false;
}
//获取共享锁
public void lockShared() {
int arg = 1;
if (tryLockShared(arg) < 0){ //如果tryAcquireShare失败
//将当前进程放入队列
BingAQS.WaitNode node = new BingAQS.WaitNode(Thread.currentThread(), 1, arg);
waiters.offer(node); //加入队列
for (;;){
//若队列头部的元素是当前线程
BingAQS.WaitNode head = waiters.peek();
if (head!=null && head.thread == Thread.currentThread()){
if (tryLockShared(arg) >=0){ //尝试获取共享锁, 若成功
waiters.poll(); //将当前线程从队列中移除
BingAQS.WaitNode next = waiters.peek();
if (next!=null && next.type==1){ //如果下一个线程也是等待共享锁
LockSupport.unpark(next.thread); //将其唤醒
}
return; //退出方法
}else{ //若尝试失败
LockSupport.park(); //挂起线程
}
}else{ //若不是头部元素
LockSupport.park();
}
}
}
}
//解锁共享锁
public boolean unLockShared() {
int arg = 1;
if (tryUnLockShared(arg)){ //当read count变为0,才叫release share成功
BingAQS.WaitNode next = waiters.peek();
if (next!=null){
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
//留白,不是公共的逻辑
//尝试获取独占锁
public boolean tryLock(int acquires) {
throw new UnsupportedOperationException();
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
throw new UnsupportedOperationException();
}
//尝试获取共享锁
public int tryLockShared(int acquires) {
throw new UnsupportedOperationException();
}
//尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
throw new UnsupportedOperationException();
}
}
2.重新实现ReentrantLock
在ReentrantLock中调用通过子类继承BingAQS的lock()、unlock(),还有tryLock(),但是由于在BingAQS类中tryLock()是留白的,所以不能直接调用,需要重写该方法,然后在调用。重写该方法,就是根据ReentrantLock的特性来重写(根据实际需要重写)
代码如下(示例):
public class BingReentrantLock {
class ChildAQS extends BingAQS{
//不公平
public boolean tryLock(int acquires) {
//如果read count !=0 返回false
if (readCount.get() != 0)
return false;
int wct = writeCount.get(); //拿到 独占锁 当前状态
if (wct == 0) {
//判断一下,如果为队列头不,才做CAS操作,抢锁
//try开头的这些方法,其实并不是公共的逻辑
//他们经常会被修改
if (writeCount.compareAndSet(wct, wct + acquires)) { //通过修改state来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改owner为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); //修改count值
return true;
}
return false;
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
//若当前线程没有 持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException(); //抛IllegalMonitorStateException
}
int wc = writeCount.get();
int nextc = wc - releases; //计算 独占锁剩余占用
writeCount.set(nextc); //不管是否完全释放,都更新count值
if (nextc == 0) { //是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
};
BingAQS aqs = new ChildAQS();
public void lock() {
aqs.lock();
}
public boolean tryLock() {
return aqs.tryLock(1);
}
public void unlock() {
aqs.unlock();
}
}
3.重新实现ReadWriteLock
通过实例化的方式获取调用BingAQS类的lock()、unlock()、lockShared()、unLockShared()是使用BingAQS的方法,tryLock、tryUnlock、tryLockShared、tryUnLockShared这四个方法是留白方法,需要重写。
代码如下(示例):
public class BingReadWriteLock {
BingAQS aqs = new BingAQS() {
//尝试获取独占锁
public boolean tryLock(int acquires) {
//如果read count !=0 返回false
if (readCount.get() != 0)
return false;
int wct = writeCount.get(); //拿到 独占锁 当前状态
if (wct == 0) {
//判断一下,如果为队列头不,才做CAS操作,抢锁
//try开头的这些方法,其实并不是公共的逻辑
//他们经常会被修改
if (writeCount.compareAndSet(wct, wct + acquires)) { //通过修改state来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改owner为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); //修改count值
return true;
}
return false;
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
//若当前线程没有 持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException(); //抛IllegalMonitorStateException
}
int wc = writeCount.get();
int nextc = wc - releases; //计算 独占锁剩余占用
writeCount.set(nextc); //不管是否完全释放,都更新count值
if (nextc == 0) { //是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
//尝试获取共享锁
public int tryLockShared(int acquires) {
for (; ; ) {
if (writeCount.get() != 0 &&
owner.get() != Thread.currentThread())
return -1;
int rct = readCount.get();
if (readCount.compareAndSet(rct, rct + acquires)) {
return 1;
}
}
}
//尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
for (; ; ) {
int rc = readCount.get();
int nextc = rc - releases;
if (readCount.compareAndSet(rc, nextc)) {
return nextc == 0;
}
}
}
};
public void lock(){
aqs.lock();
}
public boolean tryLock(){
return aqs.tryLock(1);
}
public void unlock(){
aqs.unlock();
}
public void lockShared(){
aqs.lockShared();
}
public boolean tryLockShared(){
return aqs.tryUnLockShared(1);
}
public void unLockShared(){
aqs.unLockShared();
}
}
对应链接如下
ReadWriteLock代码实现
ReentrantLock代码实现
模板方法模式如有不懂请自行查阅相关文档
注意:在实际的AQS中只有一个值来存放writeCount和readCount,因为俩个值会存在原子性问题(就是两个判断失效,比如两个线程同时获取:writeCount,readCount然后分别进行CAS操作,两个值都能成功,这是我们不允许的,所以JDK的AQS是一个值来存储的)
总结
通过模板方法实现了代码的抽象,实现了JDK中的AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)
- JDK中的AQS的第一排方法对应我们的lock()、unlock()、lockShared()、unLockShared();
- JDK中的AQS的第二排方法对应我们的tryLock()、tryUnlock()、tryLockShared()、tryUnLockShared()
- state–>我们的writecount\readcount ;
- owner是一样的;
- Queue–>我们的LinkedBlockingQueue(只是方便)