AQS原理

提示:本文需要结合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)

  1. JDK中的AQS的第一排方法对应我们的lock()、unlock()、lockShared()、unLockShared();
  2. JDK中的AQS的第二排方法对应我们的tryLock()、tryUnlock()、tryLockShared()、tryUnLockShared()
  3. state–>我们的writecount\readcount ;
  4. owner是一样的;
  5. Queue–>我们的LinkedBlockingQueue(只是方便)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值