ReentrantLock源码解读

前言

在没接触java.util.concurrent并发包之前,大家在多线程情况下使用到的都是synchronized同步锁,这是c++也就是jvm层面的锁,不方便使用者观看源码,也不能自己对其扩展。所以今天给大家来解读一下ReentrantLock锁的源码。

正文

那么ReentrantLock的源码该如何入手呢?此时本人就传授一个看源码入手点,适用于任何源码!

1.追源码之前要对他的api和底层干活的步骤有个大概了解(方便理解)。

2.编写好一段hello world代码(这里有一点要注意,比如在追springioc的执行步骤的源码时,你的hello world代码一定要打上log日志,应为配合上debug和log日志就很好分析)。

3.使用到idea的类提供的类图生成器,查看继承和实现关系,并且一个类的源码不可能只有这个类来干活,肯定是有千万层套娃(手动滑稽)。

4.一定一定要会idea的debug工具(以后会对debug工具做一个比较详细的说明帖子)

5.对于编程思想和能力比较差一点的同学可以借助到网课资源,找一个比较好一点的网课老师的课程(这里我必须对某尚某硅谷和某黑某马的课程打一波广告)对于能力比较强的同学可以找几篇点赞量高的帖子来入手,可以是看个大概也可以跟着帖子步骤一步一步追。

接下来来看一下ReentrantLock的关系图把。

ReentrantLock关系图

可以看到ReentrantLock实现于Lock,但是ReentrantLock内部维护了Sync,而Sync继承于AbstractQueuedSynchronizer,也就是常说的AQS。所以我们在深入了解ReentrantLock之前是肯定要明白AQS它到底是个啥玩意!现在我们也是成功的找到了一个入口了,那么就先往AQS里面追。

AQS内部结构图

 可能对于没了解过AQS的同学,在看到这张图的时候可能还是比较的蒙蔽,那么我来解读一下。AQS内部其实还是蛮简单的,一个状态,一个队列,一个当前执行线程的引用。

对于底子比较差或者是对AQS或者是对juc没怎么了解的同学可能还是比较蒙蔽的状态,可能现在的疑惑是AQS和我今天要追的ReentrantLock他们之间到底是什么关系?

AQS和ReentrantLock的关系好像就是 :

一个程序员开发一个网站后端。使用的框架是Spring。

这里的后端就是ReentrantLock,Spring框架就是AQS。

ps:AQS对于juc包下的技术它就是一个框架,比如ReentrantLock、CountDownLatch等等内部都是维护一个AQS。

那么带着疑惑开始追寻源码把~


前面说过追源码还要必备一个hello world的代码,然后结合idea的debug断点来追,那么就先把代码准备好吧~

注意一定要开启2个线程

可以查看到,在ReentrantLock中维护了一个公平锁一个非公平锁,在无参构造函数中创建的是非公平锁,所以接下来的操作都是基于非公平的锁。(其实非公平和公平的差别并不大,区别后面会细说)

 可以看到非公平锁上锁代码

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

知道cas自旋锁的同学一看if的判断条件就能知道这里是使用cas保证原子性。还不知道cas的同学可以去找些帖子或者是课程去了解一下,不过我这里还是对cas自旋锁来做一个简单的解释把,以后有时间会花点时间来写篇帖子来讲解cas。

cas自旋锁的解释:

对于cas来说,简单的来说就是一个乐观锁的实现,乐观锁一般是配合版本号来控制,而cas是两个参数,一个是要改变的值,一个是期望值。在多线程的情况下一起竞争一个值来改变,对于cas来说,在改变前会查看期望值是否被改变。如果已经改变了,那么他会修改失败,并且循环来尝试继续修改(自旋)。

从前面的介绍我们知道AQS内部是有一个状态值,看到compareAndSetState(1,0),我们就能知道AQS内部维护了一个unsafe对象,然后cas来改变状态的值,如果能抢到cas修改的那个线程就是幸运线程了,没抢到的就会添加到AQS内部维护的一个等待队列中,继续自璇尝试。那么接下来往里面追看看我的说话是否正确。

先进入到if成功的条件,可以看到就是把当前线程赋值给exclusiveOwnerThread,而前面AQS图里面我也有画到这个变量,他就是指向当前线程。

那我们怎么进入到else条件呢?没错就是前面我们hello world代码中写了两个线程,我们在debug控制台中切换到第二个线程

debug工具切换到第二个线程

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


// tryAcquire()  尝试一次获取到锁,但是这个不会自旋继续获取

这个源码也是比较简洁优美的,很多操作都是在if判断中执行,作者也是发挥判断条件的优势,比如这里的&&比较,如果前者已经是false就不会往后走了,只有前者为true的时候才会去判断后者,这种写法在AQS中广泛使用到!

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
   // 状态等于0也就是线程1释放掉锁,我们再次尝试获取,因为是非公平锁,所以任何线程都可以竞争
            if (c == 0) {      
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 这里就是可重入锁的获取
            else if (current == getExclusiveOwnerThread()) {    
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

从方法名就可以知道是尝试获取。我们知道ReentrantLock是可重入锁,所以这段代码其实也是在重试一次获取到锁,然后再判断是否是同一个线程,其实从这里也可以看到重入锁就是给状态值+1,那么我们就可以想象到释放锁的时候就会来给状态-1,并且来判断是否到0(实际上也是这样的,后面会追到)。当前是线程2,肯定返回值也是false。那咱们继续往下走。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

addWaiter(Node.EXCLUSIVE)方法也是一个重点方法我们追进去。

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

此时我们想象一下:

  1. AQS中有一个head和tail对吧,那么他在哪里初始化呢?
  2. AQS的队列中不是还有一个sentinel哨兵节点么,那么它在哪里被初始化呢?
  3. 没竞争到的线程在哪里添加到队列中呢?

没错这些操作都是在这里执行!!

我们好像并没有操作过head和tail,所以if判断是肯定通过不了的。大家就可以想象到enq(node)方法是对tail和head做一个初始化。

    // node是新建的一个节点
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

解读这块最好是带好笔带好纸一步一步的追,光靠脑子想象容易断节,很影响效率.....不过问题不大总结完这边我会绘图来接同学们理解。

首先是获取到tail但是我们这里的tail为空,所以进入到第一个if代码块中这里就是初始化head,new Node(),这个节点就是我们的哨兵节点。并且tail引用head。然后进行下一次循环,此时tail已经不为空进入到else代码块中。

这里传进来的node就是当前线程的node,我们知道AQS维护的队列是一个双向链表,所以prev是指向前一个,next是指向下一个。

node.prev = t 也就是当前节点的前节点指向哨兵节点。

compareAndSetTail(t, node)这里就是tail指向于当前节点,也证实了tail指向最后一个节点。

t.next = node; 就是哨兵节点的后节点指向于当前节点。

这东西呢我觉得光靠文字是真的很难表达清楚,更别说光靠文字你们能理解,所以上图把!

初始化工作就全部完成。那么又看到非初始化代码,因为不可能此次初始化对吧。

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

又回到这里了以后来新的线程肯定是能进入到if代码块中的。那么再来解析一下代码。

node.prev = pred; 这里就是当前节点的prev指向tail也就是当前的最后节点。

compareAndSetTail(pred, node) 这里就是把tail的指向当前节点,所以当前节点就当队列中最后一个节点

pred.next = node;  目前是当前节点当最后一个节点,这里就是当前节点的前一个节点的后节点指向当前节点

其实这里可以看出操作跟enq(node)的第二个循环操作是一样的。

可能文字描述还是比较难理解,画不多说上图!

 所以追完这里就可以得出队列是一个双向链表。

其实对链表熟悉点的同学这里应该是特别的简单。

画不多说继续往里追吧,下一个方法就是acquireQueued();

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
// 获取到当前线程节点的前一个节点,从我前面那张AQS内部结构图可以知道,第一个节点是一个哨兵来干活的。
                final Node p = node.predecessor();    
// tryAcquire(arg)这里继续在尝试获取到锁,但是肯定是获取不到的,所以往下面的if走
                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);
        }
    }

看到死循环就知道这里会自旋来获取锁,那么他到底是不是一个无限的循环呢?

肯定不是啊,无限的死循环多浪费性能,那我们维护的队列的意义在哪里呢?

这里讲解一下循环里面的2个if判断语句把,第一个if也就是在再次尝试获取到锁,显然现在还是获取不到,因为线程1还没释放,所以来到第二个if判断中。前面提到AQS的作者代码是写的特别的简洁优美,这里又是这样的(我第一次看的时候是真的脑袋疼...)

进入到shouldParkAfterFailedAcquire(p, node)方法中。

   /**
    *    pred 是当前线程节点的前一个节点
    *    node 是当前节点
    */
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        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);
            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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

waitStatus:

在AQS的有一个状态值state,那么waitStatus一看就知道也是个状态值,那么他是干啥的呢?从这里一眼就知道他是AQS队列中每个Node节点的状态值。

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
static final int PROPAGATE = -3;

volatile int waitStatus;

int的默认值是0,所以waitStatus的默认值为0,所以这里的判断肯定是进入到else中,使用cas将waitStatus的值改变成Node.SIGNAL也就是-1,前面我手绘的AQS内部结构图中在AQS队列中只要不是tail指向的节点的waitStatus都是为-1,-1的意思就是提醒下一个节点苏醒(记住这个-1我们继续往下走,有时候有些内容刚开始没弄懂或者是比较模糊也没事(但是一定要记住),后面肯定有代码会再次体现出来的)所以这个返回值肯定是false了。

返回值是false也不会往后者判断走,因为是&&,所以进入到下次循环中。

tryAcquire(arg)肯定还是获取不到锁的,那么又到了shouldParkAfterFailedAcquire(p, node)方法中。因为上一次循环已经将waitStatus的值改编成-1了,所以这里也是返回了true,那么我们就继续往后者判断走。

parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

park、unpark的介绍:

没接触过AQS的同学可能没见过park和unpark。这里我就做一个很简单的介绍吧~

在java基础中大家线程中的通信应该都是使用到的sync同步锁+Object类中的wait和notify通信。这里需要使用到sync同步锁。而park和unpark功能和wait和notify一样。而这里不需要sync同步锁,底层是维护了一个unsafe类,也就是通过cas操作来代替sync同步锁。

所以这里将当前的线程给park()起来了,进入到线程的wait阶段等待被唤醒。其实到这里有经验的同学就可以知道到时候是当前线程节点的前一个节点将当前节点的线程给唤醒,不过还是要经过cas自旋来竞争,运气好才能获得到唤醒的机会,因为是非公平!


接下来我们回来t1线程,还是在debug控制台中切换到线程1

线程1开始执行业务逻辑了,业务逻辑执行完毕来到finally代码块中的unlock()方法。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 release释放的意思也就是释放锁,那么先进入到tryRelease(arg)方法中,字面意识就是尝试释放,跟之前的tryacquire(arg)方法相似。

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

第一个if判断就不多说,看到第二个if判断中,c==0也就是state的当前值为1,为1不就是没有锁重入,所以这里的判断就是判断是否存在锁重入。当然我这里是没有存在锁重入的,所以我们的c==0是成立的就把exclusiveOwnerThread线程的指向为null,并且将state值设为0,并且返回true。其实又锁重入的话就是把状态减一给赋值到state中,并且返回到业务代码中继续往下执行,直到下次finally代码块中unlock()继续执行这段代码c==0就彻底释放锁。

tryRelease()方法返回为true所以我们继续回到release()方法中继续往下执行。

      // 获取到AQS的队列的头部节点
            Node h = head;  
     // 之前在t2线程中将头部节点的waitStatus状态设置为-1了,所以if肯定能通过,所以释放锁的代码就在unparkSuccessor(h)中
            if (h != null && h.waitStatus != 0)  
                unparkSuccessor(h);   
            return true;

之前在t2线程中将头部节点的waitStatus状态设置为-1了,所以if判断肯定能通过,所以释放锁的代码就在unparkSuccessor(h)中,那咱们继续往里走。

     // node 参数就是head
     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)
            compareAndSetWaitStatus(node, 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.next 也就是获取到t2线程节点。
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

方法参数的node就是head也就是队列的头节点。所以第一个if判断肯定为true也就是将头节点的waitStatus值改成0方便后来的判断,因为如果t2唤醒了并且竞争到了锁,那么头节点将会被移除,并且t2线程的节点将成为sentinel哨兵节点。继续看到下一个if判断。

Node s获取到node.next 也就是获取到t2线程节点。因为waitStatus的默认值是0并且如果后面还有节点waitStatus的值也是-1所以当前if判断为false,继续看到下一个判断。

没错这里就是在唤醒t2线程。执行到这里整个unlock()方法全部执行结束。并且t2线程已经被唤醒了,但是并不代表是竞争到锁了,因为是非公平的。此时我们通过debug控制台进入到t2线程。


private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

回到t2,因为park过程中是可以被interrupt的,但是我们并没有去打断,所以我们返回是一个false。所以我们又回到死循环尝试获取到锁的方法中。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                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);
        }
    }

由于parkAndCheckInterrupt()方法返回是false,所以继续for循环。

此时看到tryAcquire(arg)方法,就就可以直到这是一个非公平锁了,就算在队列最前面的线程被唤醒也需要cas的方法来争取锁。

在非公平锁中,当线程unlock释放锁后,队列线程和非队列中的线程公平竞争锁的机会,通过tryAcquire()方法cas的方式来竞争。

因为我这里也不存在其他线程,所以t2肯定是能通过tryAcquire()方法来获取锁。也就是if判断为true那么我们继续看if代码块中执行了些啥把!

                if (p == head && tryAcquire(arg)) {
// 设置head的引用为t2线程的节点
                    setHead(node);
// 因为p是当前线程节点的上一个结点也就是sentinel哨兵节点,所以p.next为null,就是让p的next指向null,并且此时的head也是指向当前线程的节点,所以此时的p已经被拆出来了,等待被gc的回收。
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

因为p是当前线程节点的上一个结点也就是sentinel哨兵节点,所以p.next为null,就是让p的next指向null,并且此时的head也是指向当前线程的节点,所以此时的p已经被拆出来了,等待被gc的回收,可能口头表述比较的绕,我来画图给大家理解把!

AQS节点改变图

 并且这里返回是false,因为并没有被打断,所以我们回到最初的acquire()方法中

    // 前面从acquireQueued()方法出来,并且返回为false,所以acquire()方法也执行完毕。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

前面从acquireQueued()方法出来,并且返回为false,所以acquire()方法也执行完毕。继续往上走

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 从这个方法出来的
                acquire(1);
        }

追到这里就差不多全部追完了,执行完这个方法后就是t2线程的业务逻辑代码和unlock()释放锁的代码了,unlock()方法我们就不再追一遍了! 


不过并没有结束,我们还大概的来追一下公平锁的执行流程吧!

大家还记得ReentrantLock的2个构造方法吗?我们之前使用的是默认的构造方法也就是创建的非公平锁,还有一个构造方法是传一个boolean值进来。

    // 为true就是公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

传入true就是公平锁。还是强调一句,作者的代码真的是想尽一切办法来简介优美~!

在前面也谈到过非公平和公平锁的区别,实际上就是公平锁是走队列先进先出,非公平是队列和外界一起。所以具体的区别代码就是在获取锁的代码。那么下面就追到公平锁的获取锁。

// 公平锁
final void lock() {
   acquire(1);
}

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }



// 非公平锁
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

其实明白的同学就已经可以看出区别在哪里了,公平锁是所有并发都进入到acquire()方法,而非公平锁是直接cas竞争,所以不管是外部线程还是队列中的线程都是有机会来竞争锁。那么下面就来看一下公平锁是怎么保证公平只能按照队列的先后顺序来竞争。进入到公平锁的acquire的tryAcquire()方法中。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

有看过非公平的同学能看出哪里有区别,就是在c==0的if代码块中,那么我们截取到核心代码

if (c == 0) {
   if (!hasQueuedPredecessors() &&
       compareAndSetState(0, acquires)) {
       setExclusiveOwnerThread(current);
       return true;
   }
}

首先进入到hasQueuedPredecessors()方法中查看详情

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

本方法就是用来控制公平的,但是我们案列代码一共写了两个线程,所以线程一进来肯定t、h都为null所以return为false,而外部对方法返回值进行取反操作,所以前者都true,执行后置,追完非公平锁后就明白执行到这就是cas竞争第一把锁了,然后AQS维护的exclusiveOwnerThread就指向当前线程返回为true,acquire()方法中对tryAcquire()返回进行取反所以前者为false,所以执行完毕t1线程可以去执行业务代码了。此时我们想看出区别肯定是要通过debug控制台切换到线程二执行到park()方法,让线程去等待。

再切换到t1线程进行unlock()操作,不管是非公平锁还是公平锁其他方法都是公用都是一样的。所以这里我直接上代码,就不一步一步的步骤放上来了!

 代码就直接跳转到被唤醒后,追过非公平的同学应该都知道流程,因为两种锁其他操作都一样!

被唤醒后,会继续到tryAcquire()尝试获取锁,进入之前,大家就要考虑一个问题,公平锁是只能从队列中先后顺序的取线程的,当t1线程释放完锁后,并把t2给唤醒后,t2这里尝试获取到锁,外面来的线程也在acquire()方法的if判断中tryAcquire()方法竞争锁,那么公平锁是怎么控制的呢?

没错就是hasQueuedPredecessors()方法,我前面也说过就是这个方法来控制的,所以这里就仔细讲解。

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

此时需要我们打开脑子来想象外面的线程和t2线程在并发争取到return这里。

h!=t是肯定的,因为在目前的AQS的内部是head指向的是sentinel哨兵节点,tail是t2节点。

继续看到

((s = h.next) == null || s.thread != Thread.currentThread());

/**
  * 对于t2线程来说
*/
// h.next是t2节点并且s指向t2
// s.thread就是t2线程
// Thread.currentThread()就是t2线程
// 返回为true


/**
  * 对于外来的线程来说
*/
// h.next是t2节点并且s指向t2
// s.thread就是t2线程
// Thread.currentThread()是外来线程
// 返回为true

这里我的注释写的比较明显

所以对于t2线程来说后者返回是false

所以对于外来线程来说返回是true

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

而tryAcquire()方法对hasQueuedPredecessors()方法的返回值进行了取反,所以这里可以t2线程就作为判断的前者通过判断,进行后者的判断,而后者就是cas操作将state值来改变,此时这个cas操作是没有任何并发来操作是直接能改变成功的。而没有成功的外部线程就往下走,走完tryAcquire()方法都是返回的false,而acquire()方法if判断中对tryAcquire()方法返回值进行了取反。所以外部线程就进入到acquireQueued()方法中进行添加到队列,并且自旋操作来获取到锁。

    // 外部线程就进入到acquireQueued()方法中进行添加到队列,并且自旋操作!
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

不要忘记我们的t2线程其实也在acquireQueued()中虽然是通过tryAcquire()方法获取到锁了,但是还要一些操作需要来执行。

获取到锁以后还需要对节点做一些操作,具体的操作跟非公平锁是一样的这里我就不细说了,可以看图中的解释。

此时t2获取到锁就可以执行t2线程的业务逻辑了。并且等待t2执行完毕在finally代码块中unlock释放锁就可以迎来下一波的获取锁操作了。

总结

没啥好总结的。但是呢再最后还是得佩服写AQS的作者,真的很佩服很佩服,代码写的如此的简单优美,虽然对于初学者来说这些判断挺脑袋疼的。而且他很多代码的复用和先后顺序我也是打心底的佩服!!!!!

最后再插句嘴。后面会把所有用到AQS框架的juc包下的技术慢慢追完。觉得博主写的不错的可以点个关注不迷路。并且博主所有的手绘的图你们都可以拿去使用。代码么就是要开源~!

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值