ReentrantLock学习简介

ReentrantLock

定义

  • 一个可重入互斥锁Lock
  • ReentrantLock将由最近成功获取锁,并还没释放锁的线程拥有。当一个线程调用lock()方法时,若锁并未被其他线程占有时,则成功获取该锁并返回;若该锁已经拥有该锁,则立即返回。
  • 该锁提供了公平锁与非公平锁的机制
  • 此锁最多支持一个线程发起20+亿个递归锁,超过次限制会抛出Error

代码解读

以下是常用lock方法使用的典型代码

 class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...
    public void m() { 
        lock.lock();  // block until condition holds
        try {
        // ... method body
        } finally {
        lock.unlock()
        }
    }
 }

lock方法

public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
...
public void lock() {
   sync.lock();
}
...

Sync是ReentrantLock的内部类,它继承了AbstractQueuedSynchronizer父类,锁的核心逻辑都在这里实现。NonfairSyncFairSync(继承Sync内部类),是ReentrantLock中的另外两个内部类,里面分别实现了非公平锁和公平锁的功能。

NonfairSync 我们先从非公平锁开始,以下是NonfairSync的实现

    static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    }

compareAndSetState是AbstractQueuedSynchronizer中的方法,这也是锁基于CAS的实现。CAS即compareAndSwap,比较并交换。主要参数expect、update,比较值是否与expect,如果相等则用update的值进行替换,并发挥true。如果不相等,则返回false。它利用Unsafe类的native方法,保证了比较与交换的原子操作。

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a <tt>volatile</tt> read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  • 初始,lock并未被任何线程获取时,state的值是0。如果compareAndSetState(0,1)返回的是true,则更新state的值为1,并保存该线程的信息setExclusiveOwnerThread(Thread.currentThread()),在该线程递归获取锁时,判断可直接返回,这就实现了锁的可重入性。此时,该线程已成功获取该锁。

  • 如果在该线程获取锁时,该锁已被其他线程占有,这个时候state的值已不等于0,compareAndSetState(0,1)返回的是false.此时执行代码acquire(1),该方法是同步器父类AbstractQueuedSynchronizer的方法

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

    其中tryAcquire,是在子类中实现的,NonfairSync的实现是直接用Sync的实现

    /**
     * Performs non-fair tryLock.  tryAcquire is
     * implemented in subclasses, but both need nonfair
     * try for trylock method.
     */
    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;
    }
    

    为什么这个方法是抽象在Sync中,而不是在子类NonfairSync中呢。原因是tryLock方法就是非公平的机制,它也被tryLock所需要,当然这个也不是重点。继续,我们来看这个方法。首先获取当前state的状态0。

    • 若这个时间点state等于0,说明此时此刻已经没有任何其他线程占用该锁。此时会再次CAS获取锁。如果还是失败,则返回false
    • 若这个时间点state不等于0,则获得该锁的线程是不是当前的线程,若不是则返回false

    回到父类的acquire方法,如果该线程已经获得锁,则tryAcquire返回的是true,直接返回,后面的逻辑忽略。如果还是没有拿到锁,执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 如果说CAS是锁的关键代码,那这里的逻辑是第二关键代码(原谅理科生匮乏的词汇量��)。
    这里有一个队列(queue)的概念,那队列里是什么呢?AbstractQueuedSynchronizer对它的定义叫做节点(Node),Node是这个同步器的一个内部类。

    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        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 nextWaiter;
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        Node() {// Used to establish initial head or SHARED marker}
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    

    没错,队列中存放的就是线程。而这个Node,其实是对队列中的线程以及它在队列中的状态的一个抽象。回到之前的代码,首先看看 addWaiter(Node.EXCLUSIVE),这个方法就是给当前线程做个定义,定义成独占模式,并作为获取锁的等待者的身份进入队列中,并将该节点放在队列的尾部。都知道,队列是FIFO嘛,也必须是尾部。

    /**
     * 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);
        // 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;
    }
    

    tail不为空时,即队列中有节点时,会将当前线程Node放入队列尾部。如果由于并发,CAS返回false或者队列为空时,执行enq(node)

    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    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;
                }
            }
        }
    }
    

    无限循环,直到CAS将该节点成功放入队列的正确位置。接下来的代码

    /**
     * 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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    该方法会不间断的获取锁,直到获取到锁,或者直到该线程需要被阻塞。该方法返回的布尔指的是该线程在获取锁的等待过程中,是否需要被interrupted。这段代码没有细究,细究起来感觉要疯,大概是这意思。现在再次回到acquire方法,如果acquireQueued返回false,说明已经获取到锁,方法立即返回。如果返回true,则执行selfInterrupt()

    /**
     * Convenience method to interrupt current thread.
     */
    private static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    

FairSync公平锁,区别是tryAcquire()方法是自己的实现,列代码

/**
 * Sync object for fair locks
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    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;
    }
}

基本上跟NonfairSync差不多,主要多了hasQueuedPredecessors()方法,这个方法判断如果当前线程之前还有正在排队的线程,则返回true.否则说明该线程是第一个拥有获取锁的资格。这就是体现了“公平”。

tryLock方法

OK,到这里ReentrantLock的lock方法已经讲完了。接下来,还有tryLock()。无参的tryLock,其实就是非公平锁的实现,他区别于lock方法,只是tryLock有返回值。

 public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

tryLock(long timeout, TimeUnit unit)可以设置超时时间,超过设置的时间,即放弃获取该锁。重点代码如下

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

该代码是在父类AbstractQueuedSynchronizer同步器中,下面直接看doAcquireNanos(arg, nanosTimeout)的代码

/**
 * Acquires in exclusive timed mode.
 *
 * @param arg the acquire argument
 * @param nanosTimeout max wait time
 * @return {@code true} if acquired
 */
private boolean doAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    long lastTime = System.nanoTime();
    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;
            }
            if (nanosTimeout <= 0)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            long now = System.nanoTime();
            nanosTimeout -= now - lastTime;
            lastTime = now;
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

是不是感觉这代码好熟悉。是的,跟上面的acquireQueued(final Node node, int arg)有点类似,只不过这里加了时间的逻辑,代码也很容易理解。

关于ReentrantLock的源码的整理就是这些。总结下来即核心是AbstractQueuedSynchronizer这个同步器,简称AQS,而AQS的基础又是CAS,CAS保证原子性。同时AQS也是基于FIFO队列的实现。在队列中控制线程的状态,以及在其中的节点位置,来决定该线程获取锁的优先级等。

大龄初学者的第一次总结,硬着头皮终于写完了。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值