ReentrantLock.FairSync的lock方法源码笔记

ReentrantLock通过lock方法调用公平锁的lock方法

final void lock() {
    //调用AQS的acquire方法
    acquire(1);
}
public final void acquire(int arg) {
    //尝试获取锁
    if (!tryAcquire(arg) &&
        //如果获取锁失败,将线程通过addWaiter添加到等待队列中,然后在acquireQueued中进行自旋获取锁或park
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果status=0,说明锁是自由状态,可以尝试获得锁
            if (c == 0) {
                //判断等待队列是否没有等待线程或队列的头节点就是当前线程(即判断有没有必要去尝试获取锁),满足其中一种,就可以尝试获取锁
                if (!hasQueuedPredecessors() &&
                    //如果没有等待线程或者第一个线程就是当前线程,进行CAS尝试获得锁,成功则设置status=1,锁持有线程exclusiveOwnerThread为当前线程
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果锁已被持有,判断是否是被当前线程持有,是则表示锁重入场景,将status+1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //不是当前线程持有锁,加锁失败
            return false;
        }
    }
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;
        /*当队列不为空时,head节点被设计为总是代表当前持有锁的线程,是个哨兵节点,当前持有锁线程是不参与排队的,head.thread就没有用,就置null,所以第一个等待线程节点是head.next,之所以第一次入队的时候才插入首节点,是因为队列为空时,线程来加锁不需要进队列,所以在第一次入队的时候才将首节点虚拟出来
        1.tail=head分为两种情况,一种是tail=head=null,对应非竞争场景,队列为空,返回false,就不用排队直接去尝试获取锁;
          或者队列中只有一个节点,这种场景又分为两种,一种是之前在排队的线程都出队了,最后一个线程也拿到锁了,这时它就是head并且也是tail,这种情况也可以去尝试获取锁,因为它其实就是第一个等待线程,
          另一种场景是在第一次创建首节点的时候,只创建完首节点,还没插入第一个等待线程节点就进行线程切换了
        2.如果队列中不止一个线程,并且第一个等待线程不是当前线程,返回true,这种情况不可以尝试获取锁,前面的都还没获取到锁,后面的就不用尝试获取锁了;如果第一个等待线程是当前线程(这个线程又来获取锁,重入场景),返回false,则不用排队(之所以不用排队是因为可能正好当前持有锁的线程释放了锁,而第一个等待线程即当前线程就有资格去获取锁),可以去尝试获取锁
        3. 满足h != t && h.next == null的场景:
          (1)一种场景是在enq方法里创建完首节点后,还没来得及给tail赋值,线程切换了,这种情况下,结果返回true,因为这种情况也表明在当前线程之前,已经有线程来获取锁了,那么当前线程就不能去获取锁。
          (2)一种场景是在执行h != t这个判断的时候,队列中还是两个节点,但执行到(s = h.next) == null这个判断的时候,之前那个最后一个等待线程拿到了锁,结果就返回true,但是在上层方法里对结果进行了取反,也就是说在这种场景下,线程是不会马上去尝试获取锁的,按道理来讲,这种场景表明当前线程是第一个等待线程,是有资格去尝试获取锁的,我理解不去获取锁的原因是上个线程刚获得锁,不太可能立马释放掉,所以直接入队;
          (3)一种场景是当前线程进入hasQueuedPredecessors的同时,另一个线程已经更改了tail,但还没有将head的next指向tail,这也发生在enq方法中
          (4)一种场景是当前线程将head赋给h后,head被另一个线程移除队列,导致h的next为空,这种情况说明锁已经被占用*/
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
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;
        //tail不为null,说明等待队列不为空,将当前线程添加到等待队列尾部
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //等待队列为空时,先生成一个thread=null的头节点,然后再将当前线程的节点添加到等待队列,即等待队列第一次插入会生成两个节点,真正等待的第一个线程是head.next持有的线程
        enq(node);
        //返回当前线程节点
        return node;
    }
private Node enq(final Node node) {
        //死循环
        for (;;) {
            Node t = tail;
            //队列为空时,初始化一个哨兵节点,节点的thread=null,头尾都指向这个节点,这里没有return操作,会继续下一个for循环,走到else分支
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } 
            //将当前线程节点插入到等待队列尾部,返回t,这里t实际就是head
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
备注:设置哨兵节点是为了简化对边界条件的判断与维护,单独地生成一个结构对象与其他链表结点一致的结点。
例如设置了哨兵节点的等待队列中如果存在等待线程,那么head!=tail,head=tail的时候,表明队列中没有等待线程了(都为null,或者指向一个thread=null的节点,该节点是持有线程的节点,不是等待线程);但如果没有设置,则head=tail的时候,即可能是队列中没有等待线程(都为null),也可能有一个等待线程,增加了对边界条件的判断和维护。
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //死循环
            for (;;) {
                final Node p = node.predecessor();
                //如果前置节点是头节点,表明自己是第一个等待线程,之前尝试获取锁失败,自己需要入等待队列,入队后发现自己是第一个等待的线程,如果在这期间锁被释放,自己是有可能获得锁的,就能避免park操作,所以去尝试获取锁,这就是一次自旋操作
                //如果前置节点不是头节点,那么它也不需要尝试获取锁了,因为前面还有其他线程在等待
                if (p == head && tryAcquire(arg)) {
                    //说明前面线程释放了锁,当前线程获得了锁,则将当前线程设置为队头(head=node,head.thread=null, pre=null)
                    setHead(node);
                    //将释放完锁的线程节点从队列中移走
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //如果前置节点不是头节点或尝试获取锁失败,则可能park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //ws默认是0
        //Node.SIGNAL=-1
        //所以线程第一次进这个方法是执行else分支
        int ws = pred.waitStatus;
        //线程第二次进这个方法,就走这个分支,返回true,将线程park
        //为什么不第一次直接返回true而要先改变ws,1.是为了第一个等待线程能多一次自旋,尽量不park 2.ws=0也是一个必要状态,这个状态是有意义的,其他地方会用到
        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.
             */
            //将前置节点的ws置为-1,表示上个线程park了,之所以是下个线程将上个线程的状态置为-1,而不是自己置为-1,是因为只有确定了线程是park了才能置为-1,如果置为了-1但线程并没有park,那就不一致了,而如果线程已经park了,那它已经释放了CPU,就不可能自己再改变状态了
            //第一个等待的线程在退出这个方法后将进行下一次for循环,即进行第二次自旋
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
 private final boolean parkAndCheckInterrupt() {
        //线程在这里阻塞
        LockSupport.park(this);
        /*1.如果是因为调用unpark或者interrupt将线程唤醒,就从这边开始往下继续执行,这里调用interrupted方法,判断线程是否被打断,并清除了中断信号,如果返回true表明是被用户中断,在acquireQueued方法里将interrupted设置为true,再次自旋获得锁,获得锁后返回acquire方法,并通过调用interrupt方法来还原用户的中断行为,但这种还原其实是没有什么意义的,因为外界并不能处理这个中断,但对于通过调用lockInterruptibly方法来加锁是有意义的,如果用户进行了中断,这边返回true,在acquireQueued方法里就会抛出异常,外界就可以通过捕捉这个异常来处理用户中断。
          2.如果在执行LockSupport.park(this);之前线程就被打断了,执行park操作不会阻塞线程而会直接返回(park与wait的作用类似,但是对中断状态的处理并不相同。如果当前线程不是中断的状态,park与wait的效果是一样的;如果一个线程是中断的状态,这时执行wait方法会报java.lang.IllegalMonitorStateException,而执行park时并不会报异常,而是直接返回。)这里通过interrupted清除了中断标志,下一个循环再进行park时就能正常阻塞线程,而避免了在拿到锁之前一直在执行死循环。*/
        return Thread.interrupted();
    }

在可中断锁lockInterruptibly中,如果线程被中断,还会执行下面方法

 private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        //如果节点的前置节点被取消,循环先前查找到第一个未被取消的节点
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        //如果被取消节点是尾节点,将尾节点设置为未被取消的前置节点
        if (node == tail && compareAndSetTail(node, pred)) {
            //将其前置节点的下一个节点设置为null
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码没有输出ABC是因为在调用condition1.signal()之前,主线程已经释放了锁。 在代码中,主线程先获取了锁reentrantLock.lock(),然后启动了线程A、B、C,并调用condition1.signal()来唤醒线程A。但是,在调用signal()方法之前,主线程调用了reentrantLock.unlock()释放了锁。 由于线程A、B、C在启动后会尝试获取锁并进入等待状态,而此时主线程已经释放了锁,线程A无法获取到锁并继续执行,所以最终没有输出ABC。 为了修正这个问题,可以将reentrantLock.lock()方法的调用位置移动到condition1.signal()之前,确保在调用signal()方法之前保持锁的持有状态。 修正后的代码如下: ```java package com.company.calcul; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Thread1 { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Condition condition1 = reentrantLock.newCondition(); Condition condition2 = reentrantLock.newCondition(); Condition condition3 = reentrantLock.newCondition(); Thread threadA = new Thread(() -> { reentrantLock.lock(); try { condition1.await(); System.out.println("A"); condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); Thread threadB = new Thread(() -> { reentrantLock.lock(); try { condition2.await(); System.out.println("B"); condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); Thread threadC = new Thread(() -> { reentrantLock.lock(); try { condition3.await(); System.out.println("C"); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); reentrantLock.lock(); try { threadA.start(); threadB.start(); threadC.start(); condition1.signal(); } finally { reentrantLock.unlock(); } } } ``` 在修正后的代码中,主线程在启动子线程之前获取锁reentrantLock.lock(),然后启动子线程并调用condition1.signal()方法唤醒线程A,最后在finally块中释放锁reentrantLock.unlock()。这样可以确保线程A在调用signal()方法之前已经获取到了锁,并且其他线程也能正确执行并输出ABC。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值