ReentrantLock非公平锁与公平锁源码解析

这篇从源码入手,深入了解ReentrantLock的非公平锁和公平锁的实现过程。之前已经写过ReentrantLock-基础,ReentrantLock的实现基于AQS,所以还需要先了解同步器AbstractQueueSynchronizer

非公平锁

	ReentrantLock lock = new ReentrantLock(false);
    try {
        lock.lock(); //加锁
        TimeUnit.SECONDS.sleep(1);//模拟业务处理用时
    } catch (InterruptedException e) {
		...
    } finally {
        lock.unlock();	//释放锁
    }

非公平锁获取锁的流程如下

img

1.NonfairSync.lock()

尝试以CAS方式将state从0更新为1。在ReentrantLock语境下,state=0表示当前锁未被任何线程持有,如果获取成功,将当前线程标记为锁的持有线程,加锁过程结束。如果失败,执行acquire(1)方法。

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

setExclusiveOwnerThread方法定义在抽象类AbstractOwnableSynchronizer中,和ReentrantLock类在同一包下

private transient Thread exclusiveOwnerThread;	//当前持有锁的线程
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

调用AbstractQueueSynchronizer#tryAcquire方法。tryAcquire(),addWaiter()和acquireQueued()这三个方法封装了加锁流程中的处理逻辑。

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

在AQS中,tryAcquire是定义好的钩子函数,直接调用此函数会抛出异常,需要在子类继承AQS后重写该方法。

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

具体实现由在ReentrantLock中,如果当前锁的状态为0,尝试通过CAS操作获取锁,将当前线程标记为锁的持有线程,加锁过程结束;否则判断是否当前线程和持有锁的线程相同,相同说明该锁被重入了,将state的值+1,获取成功;否则获取锁失败。

这里加入了判断锁状态是否为0的判断,如果在执行代码的过程中,之前占有锁的线程将锁释放,这里就可以直接获取锁,虽然降低了代码的可读性,但是提升了性能。后面类似代码,也是相同原理。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    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;
}

如果这里返回true,那么if语句中判断就不会继续执行后续判断,如果返回false,则会继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

3.AbstractQueueSynchronizer.addWaiter(1)

此方法是将获取锁失败的线程加入安全队列

/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);	//创建新节点,放入当前线程,mode为null
    // 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);	//插入Node至同步队列
    return node;
}

如果队列为空,使用CAS方式构造新的空头结点,如果交换成功,则通过非同步的方式将尾节点指向头节点;否则尝试将尾节点指向当前节点,原尾节点的next指向当前节点。

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;
            }
        }
    }
}
//更新头节点时,原值必须为null
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

最外层的空条件for循环保证了所有获取锁失败的线程都能加入同步队列中,当队列为空时要先进行处理。需要注意的是,Node直接不能直接插入空队列,阻塞的线程由先驱节点进行唤醒。所以要插入一个空的Node作为Head,当锁释放时由Head来唤醒后续被阻塞的线程。所以Head可以表示当前获取锁的线程,但不一定真实持有该线程实例。

4.AbstractQueueSynchronizer.acquireQueued(node, 1)

线程加入同步队列后,会一直进行循环,尝试获取锁。在循环中,有两个if,第一个判断当前线程所在节点的前驱节点是否为头节点,如果是则继续尝试获取锁,获取成功,则设置当前节点为head,退出当前循环。第二个if判断是否需要阻塞当前线程。

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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)) {	//如果前驱节点为head,则继续尝试获取锁
                    setHead(node);		//获取成功,将当前节点指向头结点
                    p.next = null; // help GC	
                    failed = false;
                    return interrupted;		
                }
                //判断是否需要阻塞该线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())	
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法用来判断是否需要阻塞,如果pred节点为SIGNAL,则返回true;如果为CANCELLED,则回溯到不为CANCELLED的节点,并将此节点作为当前节点的prev节点;如果为初始化状态(0),则通过CAS操作将前一个节点状态改为SIGNAL。

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;
}

parkAndCheckInterrupt用于上面方法返回true时,进行阻塞线程,底层调用的LockSupport.park。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
非公平锁总结

以上就是非公平锁获取锁的全部流程,非公平锁之所以不公平,是因为两个原因:

  1. 线程并非在加入队列后才有机会获取锁,在加入队列之前会尝试获取锁
  2. 线程释放锁时,先修改state状态值,然后唤醒后续节点线程

如果在释放锁之后,其他线程尝试获取锁并获取成功,那么唤醒的后续节点会继续在队列中等待。甚至后续节点需要等待唤醒,在唤醒之后还要检查pred节点是否为head节点,相比之下未进入等待队列的节点获取锁的优势更大。这也就是为什么,在高并发的情况下,就会出现饥饿问题。

公平锁

公平锁相对非公平锁,会有更严格的条件限制。在同步队列中有线程已经等待的情况下,所有线程必须先加入同步等待队列。按照队列顺序依次获得锁。

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;
}
    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());
    }

虽然公平锁保证了获取锁的顺序,但也增加了获取锁的成本。

公平锁与非公平锁的对比

非公平锁对锁的竞争是抢占式的(已经进入队列的除外),线程在进入等待队列前可以进行两次尝试,这大大增加了获取锁的机会。好处体现在两个方面:

  1. 线程不必加入等待队列,免去加入队列的操作,还节省了线程阻塞唤醒,上下文切换等开销。
  2. 减少CAS竞争,虽然CAS操作不会导致失败线程挂起,但不断尝试交换对CPU的浪费也不能忽视。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值