详解AQS以及ReentrantLock原理

AQS原理

全称是AbstractQueuedSynchronizer,是阻塞式锁和相关同步器的框架

特点:

  • 使用state属性去表示资源的状态(分为独占和共享),子类需要去定义如何去维护这个状态,控制如何获取锁和释放锁
    • getState:获取state状态
    • setState:设置state状态
    • compareAndSetState:用CAS机制设置state状态
    • 独占模式是只允许一个线程去访问共享变量,而共享模式可以运行多个线程去访问共享变量
  • 提供了基于FIFO等待队列(java实现),类似于Monitor的EntryList(c++实现)
  • 条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet

子类主要实现的方法(默认抛出UnspoortOperationException):

  • tryAcquire:尝试获取锁——只会尝试一次
  • tryRelease: 尝试释放释放锁

注意:AQS中去阻塞和恢复阻塞使用的是park/unpark机制

下面我们去使用AQS自定义一个不可重入锁

class MyLock implements Lock{
    //独占锁 同步器类
    class MySync extends AbstractQueuedSynchronizer{
        @Override//尝试获取锁
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0,1)){
                //加上了锁,并且设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override//尝试释放锁
        protected boolean tryRelease(int arg) {
            //因为释放锁不会发生锁竞争,所以不用使用CAS
            setExclusiveOwnerThread(null);
            //注意:State加了voletile关键字,所以把它放在后面修改,添加写屏障,确保前面的不可重排
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() ==1;
        }

        private Condition newCondition(){
            return new ConditionObject();
        }
    }
    private MySync sync = new MySync();
    @Override //加锁
    public void lock() {
        sync.acquire(1);//会调用tryAcquire方法(不成功则进入等待队列)
    }

    @Override   //加锁,可打断
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override    //尝试获取锁
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override   //尝试获取锁,带超时时间
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(2));
    }

    @Override   //解锁
    public void unlock() {
        //release会不仅会调用tryRelaese将状态置为0,Owner置为null,
        //还会唤醒阻塞队列中阻塞的线程
        sync.release(1);
    }

    @Override   //新建条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

ReentrantLock

ReentrantLock内部也维护了一个继承于AQS的sync类,但是它是抽象的,子类有公平Sync和非公平Sync


非公平锁的实现原理

加锁解锁流程

从构造器看,ReentrantLock默认为非公平锁

加锁

调用的是sync的lock方法,如下是非公平锁的lock方法源码:

当没有竞争时如下图所示:

当第一个竞争出现的时候:

  • 当CAS尝试将State由0修改为1,结果失败了
  • 然后回进入tryAcquire逻辑,这时state已经是1,结果仍然失败
  • 然后会执行与语句中的addWaiter逻辑,再head中构造Node队列
    • 下图中的黄色三角表示该node的waitStatus状态,其中0为默认正常状态
    • Node是懒惰创建的
    • 第一次创建的时候会创建两个Node节点,第一个是空的,称为哨兵,用来占位,并不关联线程
  • 此时当前线程还是活动的

acquire代码如下:

当前线程进入acquireQueued逻辑

  • acuireQueued会在一个死循环中不断尝试获得锁,失败后进入park阻塞
  • 如果当前线程的前一个结点是head,那么再次tryAcquire尝试获取锁,当然这时state仍然为1,失败
  • 进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus改为-1(表示当前线程进入阻塞,且该结点有责任将当前线程唤醒),返回false

  • shouldParkAfterFailedAcquire执行完之后,又循环再次tryAcquired尝试获取锁,这时任然失败
  • 当再次进入shouldParkAfterFailedAcquire的时候,这时前驱结点的waitStatus已经为-1,这次返回true
  • 进入parkAndCheckInterrupt,Thread-1 park被阻塞,Thread-1 park用灰色标记

acquireQueued源代码如下所示:

再次有多个线程经历上述过程竞争失败,就会变成下图这样

然后当Thread-0释放锁时,进入tryRelease流程,如果成功

  • 设置exclusiveOwnerThread为null
  • 设置status为0

如果当前队列不为null,并且head的waitStatus=-1,进入unparkSuccessor流程,找到队列中离head最近的一个Node,unpark恢复其运行Thread-1,会把当前结点设置为哨兵结点,且相关信息设置为null,前面的哨兵结点移除

回到Thread-1的acquiredQueued流程

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread为Thread-1,state=1
  • head指向刚刚的Thread-1所在的node,该node被清空Thread
  • 原本的head因为从链表断开,而可以被垃圾回收

但是如果这时候有其他线程过来竞争(非公平锁的体现),例如这时Thread-4来了

如果不巧又被Thread-4占了先

  • Thread-4被设置为exclusiveOwnerThread,state=1
  • Thread-1再次进入acquireQueued流程,获取锁失败,重新进入park阻塞

锁重入的原理

尝试加锁的时候会获取到State,如果已经获取了锁,线程还是当前线程,就表示发生了锁重入,会将state自增然后返回true

尝试释放锁时,先将state-1,设置一个标记位位false,如果c==0的话再将标记设置为true,然后将OwnerThread设置为null,否则的话返回false,表示只是让锁重入的计数-1,而不是真正释放锁


可打断原理

在默认情况下,ReentrantLock是不可打断模式,即使被打断,再在将打断标记设置为真之后,仍然会循环尝试获取锁,只有获取锁成功之后才会返回打断标记,进行打断

我们来看看可打断模式的源码:

可打断锁在阻塞被打断之后,会抛出异常,而不是设置打断标记,抛出异常就会终止循环,停止等等锁


公平锁的原理

非公平锁是因为插队,锁刚放开,队列中的线程还没有被唤醒,或者唤醒了还没有去抢占锁,突然新来的线程插队拿到了锁

公平锁和非公平锁的区别:


条件变量实现原理

每个条件变量就对应着一个等待队列,其实现类是ConditionObject

await流程

开始Thread-0持有锁,如果调用await进入ConditionObject的addConditionWaiter流程创建新的Node状态为-2(Node.CONDITION),关联Thread-0,加入等待队列尾部

await的源码如下:

addConditionWaiter的源码如下:

然后会进入fullyRelease流程将NonfairSync中的state设置为0,OwnerThread置为null,释放同步器上的锁

fullyRelease里会调用release方法,unparkAQS队列中的下一个节点,竞争锁,假设没有其他的竞争线程,那么Thread-1竞争成功

release源码如下:

然后会调用park方法将当前线程Thread-0阻塞住


signal

假设线程1要唤醒线程0,那么就需要调用signal方法,signal方法首先会判断是否是锁的持有者,且判断阻塞队列中的第一个Node是否为空,如果不为空会去调用dosignal方法

signal源码如下:

进入ConditionObjecy的doSignal流程,取得条件变量中的第一个Node,就是Thread-0所在的Node,将它从双向链表中断开

然后执行transferForSignal流程,将Node加入AQS的队列尾部,将Thread-0的WaitStatus改为0,Thread-3的waitStatus改为-1

doSignal的源码如下:

图片素材from黑马程序员

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值