【多线程】ReentrantLock--公平锁源码分析

 

ReentrantLock lock = new ReentrantLock(true); 
lock.lock();

调用lock()方法实际调用sync内部类的lock方法,Sync类的的lock方法为抽象方法,实际调用其子类的lock方法,由于创建的是公平锁,所以,最终调用FairSync的lock方法

public void lock() {//ReentrantLock#lock     
    sync.lock();//调用公平锁的lock 
}

FairSync的lock方法中调用了acquire方法,尝试获取锁,acquire()是AQS(AbstractQueuedSynchronizer)中的实现

Sync继承自AQS,FairSync继承自Sync

final void lock() {//FairSync#lock
    acquire(1);
}

AQS的acquire方法,先尝试去获取一次锁,如果获取失败,将该线程封装为node节点,添加到AQS阻塞队列的队尾,并以自旋的方式去尝试获取锁,如果在获取锁的过程中出现异常,自我中断(selfInterrupt())

public final void acquire(int arg) {//arg=1
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //acquireQueued的返回值实际是该线程在阻塞中是否有被中断,如果被中断了,返回true,此时再进行中断
        selfInterrupt();
}

先看selfInterrupt(),调用了当前线程的interrupt()方法进行中断

private static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

再回过头看acquire方法中的tryAcquire方法,该方法用来先去尝试能否获取锁,该方法是需要AQS的子类去重写的,AQS中的此方法,只抛出UnsupportedOperationException异常

FairSync#tryAcquire,先判断资源状态,如果未被持有,则再判断是否有线程在等待资源,如果是,获取锁失败,如果没有线程等待,尝试持有;如果资源以及被线程持有,判断持有者是不是自己,如果是自己,重入数+1,否则获取失败

protected final boolean tryAcquire(int acquires) {//acquires=1
final Thread current = Thread.currentThread();//获取当前线程
    int c = getState();//获取资源状态
    if (c == 0) {//资源没有被锁定
        if (!hasQueuedPredecessors() &&//判断在此线程前是否有等待时间更长的线程,因为是公平锁,所以如果有线程在配对等待获取锁,则该线程不能立即获得锁
            compareAndSetState(0, acquires)) {//CAS操作,如果没有等待的线程,尝试去获取锁
            setExclusiveOwnerThread(current);//没有等待线程,当前线程获取了锁,那么将资源持有者设置为此线程
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) { //资源被锁定,判断当前线程是否是锁持有者,如果是,重入数+1
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;//资源状态不为0,其持有者也不是该线程,则获取失败
}

下面是判断线程前是否有其他线程等待资源的方法:hasQueuedPredecessors()

/**
 * Queries whether any threads have been waiting to acquire longer
 * than the current thread.
 */
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; //tail阻塞队列的队尾节点
    Node h = head;//head:阻塞队列的队首节点
    Node s;
    // h != t 头节点和队尾节点不是同一个(只有当阻塞队列中没有节点时,tail和head都指向null)
    //(s = h.next) == null 判断头节点的下一个节点是否存在
    //s.thread != Thread.currentThread() 如果头结点的下一个节点操作,判断这个节点里的线程是否为当前线程
    //AQS中的阻塞队列是以Node为节点的双链表结构
    //其头节点是一个伪节点,头节点的下一个节点即队首节点是存放第一个阻塞线程的节点
    //所以源码中用头节点的next节点中的线程与当前线程做比较
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

到此,tryAcquire()的逻辑已全部分析,接下载再看如果获取锁失败后的处理

获取锁失败,先将线程包装成node节点,添加到阻塞线程的队尾

/**
 * Creates and enqueues node for current thread and given mode.
 */
private Node addWaiter(Node mode) {//AbstractQueuedSynchronizer#addWaiter
    Node node = new Node(Thread.currentThread(), mode);//将线程包装成Node对象
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;//获取队尾节点
    if (pred != null) {//如果CLH队列不为空,将新节点加入到队尾
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //只有当阻塞队列为空时,才会调用此方法来添加节点
    enq(node);//如果CLH队列为空,自旋的方式添加节点(head节点和该线程节点)
    return node;
}
/**
 * Inserts node into queue, initializing if necessary. See picture above.
 */
private Node enq(final Node node) {//AbstractQueuedSynchronizer#enq
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
        //第一次遍历
        //判断尾节点为空,说明整个队列是空的,添加头节点,将队头和队尾都指向此节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //第二次遍历
            //新增节点的pre节点指向当前队列的队尾节点
            //将自己设置为队尾节点,将队尾节点的next节点指向自己
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

将线程添加到队尾后,接下来线程以自旋的方式尝试获取锁

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 */
final boolean acquireQueued(final Node node, int arg) {//AbstractQueuedSynchronizer#acquireQueued
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//获取当前线程节点的上一个节点
            if (p == head && tryAcquire(arg)) {
                //判断他的上一个节点是否是头节点,如果是,就再次尝试获取锁
                //如果获取锁成功了,把自己置为头节点
                //上面有提到,头节点是伪节点,队列中真正的第一个阻塞线程实际在第二个节点
                //所以,判断如果自己的上一个节点是头节点,说明自己就是第一个被阻塞的线程,就可以去获取锁了
                setHead(node);//prev,thread的引用都指向null
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //不是头节点,或者获取锁失败了,进入一下逻辑
            //1.确保这个节点前的节点的状态是SIGNAL,这样自己才能安心的被阻塞,因为会被前一个节点叫醒
            //2.调用LockSupport.park对线程阻塞
            //3.在线程被唤醒时,返回这个线程的中断状态
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //如果线程在阻塞期间被中断后,不响应中断,只是设置一个是否中断的变量为true
                //最后在成功获取锁后返回这个中断状态给其调用者
                interrupted = true;
        }
    } finally {
        //最后在返回的时候判断下获取锁的这个自旋过程中是否有发生异常,如果有,则将节点从队列中移除
        if (failed)
            cancelAcquire(node);
    }
}

做阻塞前的保障工作

/**
 * Checks and updates status for a node that failed to acquire.
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev. 
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
    //判断上个节点的状态,只有在状态是SIGNAL的时候,才能确保上一个线程用完锁后会通知自己来获取锁
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        //ws > 0,即CANCEL状态,将自己前面所有是此状态的线程节点全部移除队列
        /*
         * 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.
         */
         //如果是其他的状态,将节点的状态改为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

阻塞线程

/**
 * Convenience method to park and then check if interrupted
 *
 * @return {@code true} if interrupted
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//此方法调用后,线程会在这里阻塞,直到上一个线程唤醒它
    return Thread.interrupted();
}

再回头看acquireQueued()的方法中的cancelAcquire()方法,此方法我的理解是在acquireQueued()出现异常时才会调用,因为如果正常执行,当轮到此线程获取锁,并成功的获取锁后,会将failed置为false,这样的话是不会触发cancelAcquire()方法的,而且此方法的出口,只有成功获取锁后的一个出口

/**
 * Cancels an ongoing attempt to acquire.
 *
 * @param node the node
 */
private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;//释放线程引用
    Node pred = node.prev;
    while (pred.waitStatus > 0)//让节点前所有CANCEL状态的节点退出队列
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    node.waitStatus = Node.CANCELLED;
    //如果该节点在队尾,直接将队尾置为他前面第一个不是CANCEL的节点
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //pred != head 他前面第一个不是CANCEL的节点不是头节点
        //(ws = pred.waitStatus) == Node.SIGNA 他前面第一个不是CANCEL的节点的状态是SIGNAL 
        //ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 他前面第一个不是CANCEL的节点的状态不是SIGNAL则置为SIGNAL 
        //pred.thread != null 他前面第一个不是CANCEL的节点中有持有线程
         if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            //如果pred不是头节点,且保证他的状态是SIGNAL,且他持有线程,将pred.next的与node节点的next节点连接
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

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

唤醒队列中的第一个线程

/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the 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;
        //把节点状态改为0--初始状态
        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 s = node.next;
        if (s == null || s.waitStatus > 0) {
            //检查节点下一个节点,如果为null,或者状态是CANCEL,从队尾开始,找到第一个有效的节点
            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);//唤醒队列的第一个线程
    }

到此,公平锁的lock()方法全部结束

总述

调用公平锁的lock方法,实际调用ReentrantLock中FairSync的lock()方法

他会先去调用tryLock()方法看能否先获取锁,如果获取不到,将线程添加到CLH队列里,以自旋的方式再去获取锁,获取成功后判断自己是否被中断过,然后再次中断自己

在自旋的过程中为了保证自己能被成功唤醒,他回去判断自己前面节点的状态,如果是SIGNAL,那么自己会被成功唤醒,如果是CANCEL状态,需要将节点从队列中移除,如果是其他的状态,将状态置为SIGNAL

如果在获取锁的过程中出现了异常,将其从队列中移除,如果它的头节点,需要将它的下一个节点线程唤醒

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章