AQS源码解析,并利用AQS实现一个简单的锁


AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种能实现阻塞锁和以来FIFO等待队列的同步器框架。很多同步容器都是基于其实现的。例如ReentranLock和CountDownLatch,实际上都是基于AQS实现的。

AQS核心思想就是维护一个volatile的int型变量state,用来记录状态;还有一个等待队列,用来存储陷入阻塞态的线程。

AQS基础属性

state状态

AQS维护了一个volatile int类型的变量,可以保证操作的可见性。与state相关的方法其实只有三个:

/**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
	//CAS设置state
    protected final boolean compareAndSetState(int expect, int update) {
        return STATE.compareAndSet(this, expect, update);
    }

资源共享方式

AQS中定义了两个资源共享方式:

  • Exclusive:独占式,每次只有一个线程能运行,如ReentrantLock
  • Share:共享式,每次可以又多个线程执行,如Semaphore/CountDownLatch

举例说明。如ReentrantLock中,state会被设置为0,此时是属于未锁定状态。当线程A执行lock()的时候,实际上是调用tryAcquire()独占该锁,并将state+1,此时如果有其他的线程想要执行tryAcquire()函数,就会被加入到阻塞队列中等待被唤醒了。

再如CountDownLatch。如果构建CountDownLatch实例时传入的对象是N,那么state也会被设置为N。在执行countDown()方法的时候,会执行tryReleaseShare()方法,让state-1.若其他线程B执行了await()方法,state却>0,那么线程B就会进入阻塞队列之中。

源码详解

队列中节点状态waitStatus

在AQS中,每一个线程在进入队列之前,都会被封装成一个Node结点。Node结点除了线程,还包含了五种状态:

  • 0:结点刚入队时的默认值。
  • CANCELLED(1):结点已经取消调度。当发生超时或者被中断后,结点会变成该状态,该状态的转变是不可逆的。
  • SIGNAL(-1):表示后继结点正在等待当前结点的唤醒。在一个结点入队之后,它必须要确保把前驱结点状态改变为SIGNAL之后,它才会安心地被LockSupport调用park()。否则它会面临不被唤醒的风险。
  • PROPAGATE(-3):主要使用在共享模式下。当前结点不仅会唤醒后继结点,还会唤醒后继的后继结点。

方法详解

1.acquire(int)

该方法一般用于独占模式。也正是lock()方法的语义。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  //尝试获取资源,若成功则直接返回(此方法需要内部类Sync中重写,如果需要使用的话)
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //addWaiter使线程加入队列 acquireQueued使线程阻塞
       selfInterrupt();
}
1.1tryAcquire(int)
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

该方法直接抛出异常,是因为需要子类进行重写。而之所以不声明为抽象方法,是为了不强制让子类实现,如果有需要的话才实现。

1.2 addWaiter(Node)

该方法就是把线程添加到等待队列的末尾。

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);
        //自旋
        for (;;) {
            Node oldTail = tail;
            //若尾节点不为空
            if (oldTail != null) {
                //将尾节点设置为自己的前驱结点
                node.setPrevRelaxed(oldTail);
                //CAS将自己设置为新的尾结点
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
                //若尾结点为空,则创建该一个新的队列
            } else {
                initializeSyncQueue();
            }
        }
    }

1.3.acquireQueued(Node, int)

该方法主要就是将加入队列的线程阻塞。

    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            //自旋,直到该结点获取到资源
            for (;;) {
                //获取前驱结点
                final Node p = node.predecessor();
                //若前驱结点是头结点(说明下一个就要轮到自己了),且获取资源成功
                if (p == head && tryAcquire(arg)) {
                    //将自己设置为头结点(所以意思就是说,头结点就是正在使用资源的结点)
                    setHead(node);
                    //有助于GC
                    p.next = null; // help GC
                    return interrupted;
                }
                //若前驱结点不是头结点,说明要真正地进行阻塞了
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

1.4 shouldParkAfterFailedAcquire(Node, Node)

该方法就是前面所说的:要保证前驱结点的状态是SIGNAL,否则会没有人来唤醒。

   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
       //获取前驱结点
        int ws = pred.waitStatus;
       //若已经被设置为SIGNAL了,直接返回true
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
       //若前驱结点已经取消了,说明要继续往前寻找下一个可用的
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //找到了可用的结点,设置next指针
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //CAS
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

1.5parkAndCheckInterrupt()

到了这一步,就是真正的调用LockSupport.pork()方法来使线程进入waiting状态了

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); //使线程进入waiting状态
        return Thread.interrupted(); //返回是否被中断
    }

OK!走到这里,acquire相关操作就已经基本走完。其实总结下来也就两句话:

如果能够获取到资源,就直接返回,否则就加入队列,等待资源可用再被唤醒。

说完了获取或阻塞。那接下里就要讲一下释放并唤醒了

2.release()

此方法就是在独占模式下释放资源。若释放之后state==0。说明资源可用,就会到队列中唤醒结点。这也正是unlock()的语义

    public final boolean release(int arg) {
        //如果释放资源成功
        if (tryRelease(arg)) {
            Node h = head;
            //如果头结点不为空且不是新建态
            if (h != null && h.waitStatus != 0)
                //唤醒头结点
                unparkSuccessor(h);
            return true;
        }
        //如果释放资源失败,返回false
        return false;
    }

2.1.tryRelease(int)
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

同样的,该方法还是需要子类去实现。在实现的时候需要注意。如果state变成0了,要返回true,否则返回false。因为AQS中会基于tryRelease(int)的返回值来决定是否要进行线程唤醒。

2.2.unparkSuccessor(Node)
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        //取该结点的后继结点,后继结点就是真正要唤醒的结点
        Node s = node.next;
        //若s为空或者已经被取消,则要重新遍历队列来寻找
        if (s == null || s.waitStatus > 0) {
            s = null;
            //找到最靠前的一个p,作为s
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            //唤醒p
            LockSupport.unpark(s.thread);
    }

该方法主要是要用来唤醒线程

OK!release()相关的方法也已经说完了,其实也很简单。就是释放资源,如果释放之后state为0,要从队列中唤醒线程。

应用AQS来模拟实现一个ReentrantLock

package com.jay.test;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyReentrantLock {

    private Sync sync=new Sync();

    private static class Sync extends AbstractQueuedSynchronizer{

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

        //重写tryAcquire()
        @Override
        protected boolean tryAcquire(int arg) {
            //如果CAS获取锁成功
            if (compareAndSetState(0,1)){
                //将持有线程设为自己
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            //否则就返回false
            return false;
        }

        //重写tryRelease()
        @Override
        protected boolean tryRelease(int arg) {
            //如果没有上锁,无法解锁
            if(getState()==0)
                throw new IllegalMonitorStateException();
            //如果不是当前线程在持有锁,也报错
            if(getExclusiveOwnerThread()!=Thread.currentThread())
                throw new  IllegalThreadStateException();
            //初始化
            setExclusiveOwnerThread(null);
            setState(0);

            return true;
        }
    }


    public void lock(){
        sync.acquire(1);
    }

    public void unlock(){
        sync.release(1);
    }

    public void tryLock(){
        //需要马上返回结果,所以直接调用tryAcquire(),没有"线程进入队列并阻塞"这一操作
        sync.tryAcquire(1);
    }
    public boolean isLocked(){
        return sync.isHeldExclusively();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值