JUC锁基石-AbstractQueuedSynchronizer

提到juc,很多人会觉得陌生又熟悉,因为这个包在一般情况下用不到,但是在逛博客、面试、阅读书籍的时候又很容易看到他们的身影,因为这个包几乎和java的并发编程牢牢的绑在了一起,提到并发编码就必定会谈到Doug Lea的juc包,而并发编程又是java编程的重难点之一,总而言之,使用java,你就绕不开并发编程,也绕不开juc,无论你用或者不用,它就在这里,让你时而明白,时而困惑,今天我们就一起来探究构起java.util.concurrent大厦中关于锁最重要的基础类AbstractQueuedSynchronizer。

1、AbstractOwnableSynchronizer

进到AbstractQueuedSynchronizer,我们首先看到,该类继承了另一个抽象类AbstractOwnableSynchronizer

进到AbstractOwnableSynchronizer,我们可以发现,AbstractOwnableSynchronizer并没有太多的内容,只是定义了一个用来保存当前锁拥有者的属性exclusiveOwnerThread,这个类没有太多可看的,还是回到AbstractQueuedSynchronizer吧

2、内部类Node

AbstractQueuedSynchronizer翻译过来就是队列同步器,那我们自然大致能明白这个类的主要功能了,所以在接入逻辑方法学习前,首先来看看AbstractQueuedSynchronizer中定义的队列的数据结构Node。

2.1、属性
		//共享模式
        static final Node SHARED = new Node();
		//独占模式
        static final Node EXCLUSIVE = null;
		/**
		 *用来保存节点是共享还是独占锁的模式类似于mode
		 *在使用condition是用来指向下一个节点,所以这个
		 *字段相当于将mode和nextWaiter合并到了一起
		 */
        Node nextWaiter;
        
		//取消状态、表面不参与进行锁竞争
        static final int CANCELLED =  1;
        //标明在释放锁时,是否需要唤醒后续节点
        static final int SIGNAL    = -1;
		//标明线程在等待条件队列
        static final int CONDITION = -2;
		//标明下一个获取锁使用共享的模式,并且可以无条件的往下传播
        static final int PROPAGATE = -3;
		//等待状态
        volatile int waitStatus;

     	//对前一个节点的引用
        volatile Node prev;

        //后一个节点的引用
        volatile Node next;

        //节点所包含的等待行程信息
        volatile Thread thread;

上面即为Node的全部属性、为了更方便的说明,对某些属性的位置做了调整,并且每个属性都给你对应的中文说明,相信大家一看就能明白,这是我再补充一些简单的说明,

1、SHARED、EXCLUSIVE用来说明获取锁的模式是独占式还是共享式的,在获取node对象时,通过将SHARED或者EXCLUSIVE赋值给nextWaiter来标记该节点获取锁的方式

2、CANCELLED、SIGNAL、CONDITION、PROPAGATE是waitStatus的取值范围,他们的含义也已经标明了,可能单看注释还不能完全理解,后面我们在方法中就能看到这四个值对处理逻辑的影响

3、prev、next分别是对前一个和后一个节点的引用,通过这两个字段完成各个节点之间的链接,形成队列。如下图:

image-20200911112713781

2.2、方法
    //是否是共享模式    
    final boolean isShared();
    //获取前一个节点
    final Node predecessor() throws NullPointerException
    //默认构造器
    Node() ;
    //指的获取锁模式的构造器mode:SHARED/EXCLUSIVE
    Node(Thread thread, Node mode);
    //指的等待状态的构造器,在使用Condition时初始化调用
    Node(Thread thread, int waitStatus)
    

Node提供的方法只有两个,作用也很清晰,这里不再赘述,让我们继续往下看

3、AbstractQueuedSynchronizer属性

在了解了队列的数据结构后,接下来就可以开始进行重要属性的了解了,先看Node,也是为了清除在理解属性时的障碍。

 	//指向队列的首节点
    private transient volatile Node head;

    //指向队列的尾结点
    private transient volatile Node tail;

    //锁状态
    private volatile int state;
    //等待超时的最小时间,在指定等待超时时间时使用
    static final long spinForTimeoutThreshold = 1000L;

属性简简单单只有四个,不知道这是不是侧面说明了Doug Lea的牛逼,这么重要的一个基类,只定义了简简单单3个属性,既然是队列同步器,那整个过程肯定就是对队列的操作了,这里通过head、tail完成AbstractQueuedSynchronizer对整个队列的控制,如下图

image-20200911115307766

4、AbstractQueuedSynchronizer方法

了解完基本的属性后,就可以开始咱们的正菜了,属性只是基本,对这些属性的操作才是真的干货,这里就不像属性那样一个个的进行注释说明了,这里我先列出最主要的几个方法,然后咱们一起来一个一个的研究,学习。

//独占式获取锁
public final void acquire(int arg);
//独占式获取锁,响应中断
public final void acquireInterruptibly(int arg);
//独占式获取锁,支持设置超时时间
public final boolean tryAcquireNanos(int arg, long nanosTimeout);
//释放独占式锁
public final boolean release(int arg);
//共享式获取锁
public final void acquireShared(int arg);
//共享式获取锁,响应中断
public final void acquireSharedInterruptibly(int arg) throws InterruptedException;
//共享式获取锁
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException
//释放共享式锁
public final boolean releaseShared(int arg);
4.1、acquire独占式获取锁
    public final void acquire(int arg) {
        //尝试获取锁
        if (!tryAcquire(arg) &&
            //获取锁失败,加入获取锁的队列
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

首先第一步就是获取锁,这里我们进入到tryAcquire,看看实现逻辑

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

what,直接抛了一个异常?这是什么鬼!其实这就体现了AbstractQueuedSynchronizer作为基础类的设计了,对于获取锁,AbstractQueuedSynchronizer只是提供了一个模版方法,对应的实现细节由具体的子类进行实现,这样就能够通过不同的子类实现,设计出不同的锁特性,比如读写锁、可重入锁,而AbstractQueuedSynchronizer负责处理复杂的队列管理,我们来简单看看ReentrantLock中对tryAcquire的实现:

image-20200911141941930

image-20200911142145095

从截图可以看出,ReentrantLock的内部类sync继承了AbstractQueuedSynchronizer,而内部类NonfairSync继承了sync,在NonfairSync中完了了对tryAcquire的重写

static final class FairSync extends Sync {
        protected final boolean tryAcquire(int acquires) ;
}

这里我们先不探究tryAcquire的实现细节,我们只需要明白tryAcquire的实现细节在其子类,AbstractQueuedSynchronizer只是帮助子类完成了复杂的等待队列管理,那么我大致梳理一下使用ReentrantLock进行锁操作的整个调用链,来帮助读者进行框架的了解。

ReentrantLock使用示例:

public class UseReenTrantLock {
    private ReentrantLock reentrantLock=new ReentrantLock();

    private  void method(){
        reentrantLock.lock();
        try {
            System.err.println("当前线程"+Thread.currentThread().getName()+"进入。。。");
            Thread.sleep(2000);
            System.err.println("当前线程"+Thread.currentThread().getName()+"退出。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            reentrantLock.unlock();
        }
    }
}

以上面使用reentrantLock为例,我们通过reentrantLock.lock()获取锁,然后通过reentrantLock.unlock()释放锁,我们来看下获取锁的调用链

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

这里if部分的逻辑是非公平锁的体现,与整体调用逻辑无关,我们假设if里面的逻辑失败,这时就会调用acquire(1),这个acquire正是AbstractQueuedSynchronizer中定义的那个方法,我们已经知道了,acquire的第一步就是调用tryAcquire去获取锁,由于子类ReentrantLock中有对tryAcquire的重写,所以这时就会进入到子类的tryAcquire方法中进行锁的调用,如果锁获取失败,回到acquire方法中,会通过acquireQueued进行队列逻辑处理,所以整个的调用逻辑如下

image-20200911145048986

简要说明了一下获取锁时子类与父类之间方法的调用链,我们回到主题上,继续分析acquire方法,避免大家要来回滚动,我这里再复制下代码

 public final void acquire(int arg) {
        //尝试获取锁
        if (!tryAcquire(arg) &&
            //获取锁失败,加入获取锁的队列Node.EXCLUSIVE,说明是独占式
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire获取锁,假设失败,这时便会进入acquireQueued方法,这里我们需要先看下addWaite方法。

    private Node addWaiter(Node mode) {
        //将当前队列封装为一个node
        Node node = new Node(Thread.currentThread(), mode);
        //获取最后节点的指向
        Node pred = tail;
        //如果最后节点不为空,则将新节点放到最后,并将tail指向新节点
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果尾结点为空,说明这是第一个节点
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        //循环处理,直到插入成功
        for (;;) {
            Node t = tail;
            //如果尾结点为空,初始化队列,插入空队列
            if (t == null) { 
                //使用compareAndSetHead方法,防止并发
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //将node插入到空队列后面
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

这里没什么说的,根据注释就能明白,顺便提下在enq中,初始化队列的时候,为什么不是直接将新节点node1作为首节点进行初始化,而是new一个空队列作为首节点,然后将node1放在空节点后面?这是因为在锁释放时,当前锁线程会唤醒head后面的队列进行锁竞争,如果这里初始化等待队列的时候,直接使用node1作为首节点,那么在当前获取到锁的线程释放锁时,会去唤醒head后面的节点进行锁竞争,而head指向的是当前节点,那么结果就是node1直接被跳过。

acquireQueued方法:

    final boolean acquireQueued(final Node node, int arg) {
        //获取锁时候失败
        boolean failed = true;
        try {
            //标记线程是否被中断
            boolean interrupted = false;
            for (;;) {
                //获取当前节点的上个节点
                final Node p = node.predecessor();
                /**如果上个节点是head节点,那么尝试去获取一次锁
                 *获取锁成功,则设置当前节点为head节点,返回
                 */
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //是否需要将线程挂起,如果需要,挂起线程,等待唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //如果自旋结束,未获取到锁,则取消线程获取锁,移出队列
            if (failed)
                cancelAcquire(node);
        }
    }

逻辑也不是很复杂,就是循环,自旋获取锁,当然不可能在获取不到锁的情况下一直自旋,这会消耗性能做无用功,所以在满足条件下,会挂起线程,先来看看什么情况下会挂起线程

shouldParkAfterFailedAcquire

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        /**前节点的等待状态为SIGNAL,说明在他释放锁的时候会唤醒我(当前节点线程)
         *那我可以安心的挂起,等你唤醒就可以了
         */
        if (ws == Node.SIGNAL)
            return true;
        /**
         *ws > 0,说明ws为CANCELLED,那说明前节点处于取消状态,他不会去获取锁,那更不可能
         *在释放锁的时候唤醒我,这就危险了,要是挂起了,那不是得永远挂起了
         *怎么办呢,既然你不想获取锁了,那还在等待队列里面干啥,踢出去,一直往前踢
         *直到碰到还有追求(想获取锁)的节点为止
         */
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //这里表示ws为PROPAGATE或者0(默认状态)那么就需要将他的状态设置为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

好了,shouldParkAfterFailedAcquire也不复杂,跟着我的注释应该能看的很明白了,回到外层方法,如果返回true,就会通过将当前线程挂起,直到被唤醒,到此获取锁的整个流程基本上分析完了,还差最后一个方法,当线程被中断时,获取锁失败,在finally中会调用cancelAcquire,取消获取锁,既然只剩最后一个了,当然也不能放过了,一起看看吧

private void cancelAcquire(Node node) {
    if (node == null)
        return;

    //将节点线程置空
    node.thread = null;

    // 获取当前节点的前节点
    Node pred = node.prev;
    //如果前节点的等待状态大于0(CANCELLED),向前扫码,清除掉所有已取消的点位
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    /**
     *获取pred的下一个节点,这里看似获取的predNext等价于node,但是在多行程并行的情况下不一定
     *因为有可能别的线程在后面的逻辑中替换了pred所指向节点的后节点
     */
    Node predNext = pred.next;

    //设置当前节点为已取消
    node.waitStatus = Node.CANCELLED;

    // 如果当前节点为尾结点,那么设置前节点为尾结点
    if (node == tail && compareAndSetTail(node, pred)) {
        //设置成功,则将前节点的后节点指向空,以此将node从等待队列中清除
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //前节点不为头节点且 前节点为SIGNAL或者可以设置为SIGNAL 且线程不为空
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            //获取当前节点的后节点
            Node next = node.next;
            //如果后节点不为空且后节点不为已取消,将前节点的next指向后节点,以此将node移出队列
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            /**
             *这里通过前面一系列的判断,可以断定他的前节点为head,那么就需要在此处唤醒后节点
             *因为如果此处不做唤醒操作,当node节点被移出后,再也没有节点会去唤醒后节点
             *后节点开始自旋后,会进入shouldParkAfterFailedAcquire,将node从等待队列中移除出去
             *因为node的waitStatus被设置成了CANCELLED
            */
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

在获取predNext的时候,使用pred.next,而不是直接使用node,这是因为,在多线程的情况下,有可能其他线程在执行compareAndSetNext的时候,将pred.next的指向改变了,所以有可能pred.next并不是node

至此,独占式获取锁的acquire涉及的所有代码都过了一遍了,逻辑并不复杂,主要逻辑还是在acquireQueued中,获取锁的细节代码在子类实现中。整个流程如下

image-20200911174335673

在看完acquire之后,基本上AbstractQueuedSynchronizer就完成一大半了,因为其他的方法都是在acquire逻辑上的一些扩展,区别不是很大

4.2、acquireInterruptibly独占式获取锁,响应中断

acquireInterruptibly和acquire相似,主要逻辑相同,不同的就是在处理线程中断的逻辑,首先看acquireInterruptibly方法

 public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

相比于acquire,acquireInterruptibly在线程中断时抛出了线程中断的异常,而acquire通过返回值来判断是否发生中断,如果发生中断,则恢复中断状态

再看看doAcquireInterruptibly和acquireQueued有哪些区别

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
    	/**
    	 *添加节点,模式和acquire一样,使用EXCLUSIVE独占,
    	 *只是这里将addWaiter放到了方法里面,acquire在传参时调用
    	 */
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //抛出中断异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

可以看出,和acquireQueued相比,获取锁的逻辑一致,但是在发生异常时,acquireQueued通过interrupted字段进行标记是否发生异常,而doAcquireInterruptibly直接抛出异常。

4.3、tryAcquireNanos独占式获取锁,可设置超时时间

acquireInterruptibly是在acquire的基础上增加了对线程中断的响应,而tryAcquireNanos则是在acquireInterruptibly的基础上增加了超时设置,所以tryAcquireNanos也具备中断响应的能力

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

有了acquire的学习过程,这里看起来就一目了然了,还是直接进doAcquireNanos看一下区别吧

private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    	//如果设置的超时时间小于等于0,直接退出
        if (nanosTimeout <= 0L)
            return false;
    	//获取终点时间
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //获取剩余的超时时间
                nanosTimeout = deadline - System.nanoTime();
                //如果超时,直接退出
                if (nanosTimeout <= 0L)
                    return false;
                /**
                 *判断是否需要挂起,多了一个条件就是判断剩余的超时时间是否大于设定的最小值(1000)
                 *如果大于,才进行挂起,这里主要是考虑到如果线程挂起,超时时间过短的话
                 *马上又要唤起线程进行超时退出,为了平衡线程切换和自旋的效率,所以设置了最小界限值
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    //挂起是,设置了挂起的时间
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

可以看出还是相似的逻辑,同样的味道,只是多了一些超时的判断,不同的地方都加上了注释,理解起来应该不难。

4.4、release释放独占式锁
public final boolean release(int arg) {
    	//释放锁
        if (tryRelease(arg)) {
            //释放成功后,获取head
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //唤醒head指向的下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

相比于获取锁,释放锁的逻辑就简单多了,因为释放锁的操作只有获取锁的线程可以执行,所以整个释放锁的过程都处在一个单线程操作的环境,同样的,释放锁的细节实现也在对应的实现类中进行,这里就不像获取锁那样举例了,大概的调用逻辑和获取时一样,就是unlock->release->tryRelease->release

4.5、acquireShared共享式获取锁

相对于独占锁,共享锁的特点就是可以多个线程同时获取锁,最常听说的就是读锁了,这里我们还是看代码吧

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

没有意外,获取锁的细节还是由子类进行实现,而在这个父类的学习中,我们也只需要关注等待队列的处理即可

private void doAcquireShared(int arg) {
    //创建节点加入队列中,使用SHARED,共享模式
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取前节点
            final Node p = node.predecessor();
            //如果前节点为头节点
            if (p == head) {
                //获取锁
                int r = tryAcquireShared(arg);
                //获取锁成功,设置头结点,如果中断,直接中断线程
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            //取消
            cancelAcquire(node);
    }
}

其他的获取锁的模式都是在acquire的基础上改进而来的,那我们对于其他模的式学习,也主要基于和acquire的对比来进行,因此能对acquire理解透彻的话,整个类中的其他方法理解起来就很轻松了,只不过是加法减法的问题,和acquire调用的acquireQueued相比,doAcquireShared只有一个地方不同,那就是在设置队列头的地方,一个是调用setHead,一个是调用setHeadAndPropagate,看名字就能知道,doAcquireShared这里在设置队列头时,做了更多的操作,除此以外,除了代码放置位置的调整,再无其他不同。那我们就直接看下setHeadAndPropagate多做了哪些处理

private void setHeadAndPropagate(Node node, int propagate) {
    //获取原队列头
    Node h = head; // Record old head for check below
    //设置新队列头
    setHead(node);
    //判断下一个节点是否可以被唤醒
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            //唤醒后续的共享式获取同步状态的节点
            doReleaseShared();
    }
}

因为是共享模式获取锁,锁可以被多个线程同时获取,所以在获取到锁的同时,需要去判断下一个节点是否也是共享模式,如果是的话,就需要 唤醒后续节点对锁的获取,也就是相比于setHead,setHeadAndPropagate增加的处理逻辑。

4.6 acquireSharedInterruptibly和tryAcquireSharedNanos

acquireSharedInterruptibly、tryAcquireSharedNanos相比于doAcquireShared,就和acquireInterruptibly、tryAcquireNanos相比于acquire是一模一样的,这里在单独说就有点码字数的感觉了,跳过!跳过!

4.7releaseShared

废话不多说,直接看它调用的doReleaseShared,其实在获取锁时,setHeadAndPropagate在唤醒下一个节点时,就是调用的doReleaseShared,所以这里也相当于是接着setHeadAndPropagate继续向下分析。

private void doReleaseShared() {
    for (;;) {
        //获取head
        Node h = head;
        //如果头节点不为空且头节点不为尾节点
        if (h != null && h != tail) {
            //获取头节点状态
            int ws = h.waitStatus;
            //如果为SIGNAL,则设置为初始化状态0,设置成功则唤醒下一个节点
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //唤醒节点
                unparkSuccessor(h);
            }
            //如果为初始化状态,设置为PROPAGATE
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //如果h == head,则退出循环
        if (h == head)                   // loop if head changed
            break;
    }
}

相比于独占式的release,这里的doReleaseShared要复杂一点,因为独占式的锁,在释放锁时,只有获取锁的线程可以执行,就是一个纯单线程的操作,没有太多需要考虑的,而共享式锁,多个线程共享,所以在释放时,也是多线程环境的,相比于release,会复杂一点,但看看注释,理解起来问题也不大,最后一步,因为在获取锁的情况下会修改head,所以这里有可能为false,如果为false,结合setHeadAndPropagate就知道,这里我们需要继续往下唤醒线程,所以会继续进行下一轮的线程唤醒。

总结

整个类看下来,其实最主要的就是对acquire方法的理解,其他获取锁的方式都是在这个方式上改进而来的,在开篇介绍的属性中,有一个status字段,但是整篇下来,我们都没有看到这个字段的身影,其实这个字段主要是用在具体的实现类中,用于锁状态的标记,也就是tryAcquire、tryAcquireShared需要做的事情,所以把AbstractQueuedSynchronizer理解透彻了后,学习juc中具体的锁实现就很轻松了,基本上就只需要理解实现类中对status字段的操作就可以了。AbstractQueuedSynchronizer中其实还有一个比较重要的内部类ConditionObject,他实现了Condition接口,用来作为条件队列提供更为灵活的等待 / 通知模式,但是如果这里一并讲完的话,觉得篇幅太长了,不利于阅读,所以这篇就主要写一下获取锁和释放锁的部分,之后有机会在说说Condition。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值