ReentrantLock的原理

ReentrantLock

      • 例子
      • 自己实现
      • ReentrantLock源码

例子

我们还是制造一个计数器的线程安全问题:

package reentrantlock;

public class TestMySynchronizer {
    public static void main(String[] args) {

        Counter counter = new Counter();
        //开10条线程对counter加数
        for (int i = 0; i < 10; i++) {
            new Thread(counter).start();
        }
    }

}

//一个计数器
class Counter implements Runnable {
    int val = 0;

    public int getVal() {
        return val++;
    }

    @Override
    public void run() {
        try {
        	//放大线程安全问题
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

Thread-4 is working... after increment, the value is 1
Thread-8 is working... after increment, the value is 2
Thread-0 is working... after increment, the value is 0
Thread-2 is working... after increment, the value is 0
Thread-6 is working... after increment, the value is 4
Thread-3 is working... after increment, the value is 3
Thread-7 is working... after increment, the value is 5
Thread-9 is working... after increment, the value is 6
Thread-5 is working... after increment, the value is 7
Thread-1 is working... after increment, the value is 8

当然,它是有问题的。

解决办法是什么?

加锁。

synchronize肯定能解决啦,不过我们希望在jvm层面解决同步问题。

因此,我们加ReentrantLock上锁:

//一个计数器
class Counter implements Runnable {

    ReentrantLock lock = new ReentrantLock();

    int val = 0;

    public int getVal() {
        return val++;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(200);
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

我们先用lock方法上锁,然后等这个方法返回后,我们去执行下面的代码,这期间其他线程一定是不能进来打断的。最后调用unlock方法释放锁。然后下一个线程才能够动起来。


自己实现

为了理解ReentrantLock。我们自己来写锁。

package reentrantlock;

/**
 * 简单的锁
 *
 * 对外暴露lock和unlock方法来加锁和解锁
 *
 * author:Ocean Chou
 */
public class SimpleLock {

	private AtomicInteger status = new AtomicInteger(0);

	public void lock() {
		while (!status.compareAndSet(0,1)) {

		}
		return;
	}

	public void unlock() {
		status.set(0);
	}
}


这是一把我们自己实现的锁。

我们重新走一下计数器这个task:

//一个计数器
class Counter implements Runnable {

    SimpleLock lock = new SimpleLock();

    int val = 0;

    public int getVal() {
        return val++;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(200);
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

比如10个线程进来。

首先睡200毫秒(这是为了增加抢锁的效果),然后调用lock方法。

lock方法里调用compareAndSet方法。

这时只有一个线程(比如线程1)能够操作成功(CAS操作),返回true,取反为false

于是CAS操作成功的那个线程直接return

于是执行下面的代码(val++)。

而其他线程呢?

线程1 unlock之前,它们一直在while循环里面转。


这里有个问题,就是while循环很耗资源。

 			Thread.sleep(200);
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
            Thread.sleep(5000);

如果让线程1在执行完逻辑之后睡5秒钟,那么其他线程是不是就一直外while里转了?

这样的话,cpu就标高了。

一个解决办法是使用yield();,其他线程进入while循环后,直接让出cpu的占用。

 //加锁
    public void lock(){
        //如果cas操作成功,则不进入while循环,直接return
        while(!compareAndSet(0,1)){
            yield();
        }
        return;
    }

对于两个线程的问题,这是有效的。如果有大量线程的话,比如这次你让出cpu,下次cpu调度的还是你,这样就达不到我们出让资源的初衷。

那我们试试sleep

 //加锁
    public void lock(){
        //如果cas操作成功,则不进入while循环,直接return
        while(!compareAndSet(0,1)){
            try {
                sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return;
    }

线程1只睡5秒,线程2在while循环里睡了20秒,这样就会有15秒白白被浪费了,这也不对。

现在我们用parkSimpleLock改得先进一点:

public class SimpleLock {
    //等待的线程队列
    private Queue threadQueue = new LinkedList();

    //用一个status来标识上锁是否成功
    //用volatile修饰是希望对status的改变是对其他线程可见的
    private AtomicInteger status = new AtomicInteger(0);

    //加锁
    public void lock(){
        //如果cas操作成功,则不进入while循环,直接return
        while(!status.compareAndSet(0,1)){
           mypark();
        }
        System.out.println(Thread.currentThread().getName() + " is going to lock...");

        return;
    }

    private void mypark() {
        if(threadQueue != null){
            //把当前线程加入到等待队列
            threadQueue.add(Thread.currentThread());

            //释放cpu资源
            releaseCpu();
        }
    }

    private void releaseCpu() {
        LockSupport.park();
    }

    //解锁
    public void unlock(){
       status.set(0);
        notifyThread();
    }

    private void notifyThread() {
        if(threadQueue != null){
            Object head = threadQueue.poll();
            if(head != null){
                Thread headThread = (Thread) head;
                System.out.println(headThread.getName() + " is unpark...and ready to execute the code...");
                LockSupport.unpark(headThread);
            }

        }
    }

}

注意到我们用了queue,这样子就可以一个一个按顺序来取了。

另外,我们还使用了Unsafe提供的park,它也是出让cpu资源的,但是,我们可以用unpark来人工叫醒线程。


ReentrantLock源码

好了,回到reentrantlock。

ReentrantLock的原理_第1张图片
首先是这个Sync。

ReentrantLock的原理_第2张图片
它是一个AQS

我们会重点研究。

ReentrantLock的原理_第3张图片
锁有两种,公平的和非公平的。

想象这样一个一个场景:

thread1拿到了锁,然后睡了200ms,然后此间thread2,thread3,thread4都去问有没有锁可以用啊?答案当然是否(因为thread1拿着锁嘛),于是它们按照来的先后顺序进入了一个queue。

然后thread1睡好了,准备unlock了,一unlock,thread5突然进来了,它问:“有没有锁啊?”,正好有唉,于是thread5就拿到锁了,这对于等在queue中的threads是不公平的,尤其对于最先排的thread2。

如果是按来的顺序拿到锁,这就是公平的。叫做公平锁。

非公平锁:
ReentrantLock的原理_第4张图片
逻辑就是直接cas。


我们主要关注公平锁。方法栈一路调过去:

ReentrantLock的原理_第5张图片
ReentrantLock的原理_第6张图片
比如thread1进来,getState()为0,进入if。

ReentrantLock的原理_第7张图片
这是AQS中的方法。

这个Node是AQS中的静态内部类。我们细看一下:

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

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

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        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;
        }
    }

我们只需记住:

volatile Node prev;
volatile Node next;
volatile Thread thread;

这像不像一个双向链表?

我们还是来看hasQueuedPredecessors方法。它是问:当前线程需不需要进队?

因为headtail都是null,所以h != t返回false,所以整体返回false。

所以!hasQueuedPredecessors()为true,如果cas操作成功,则

ReentrantLock的原理_第8张图片
把该线程标记成当前持有锁的线程。

然后返回true

于是!tryAcquire(arg)为false。

那么

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

将不执行selfInterrupt();方法。

然后一路return上去就完事了。

如果线程是交替执行的,也就是说,thread1完事之后,thread2再进来,那么会经历和thread1一样的代码,headtail照样是null

也就是说,交替执行,我们都碰不到底层代码。

这就是一把轻量级锁。它不像synchronize,不论是什么情况,都会调os函数(当然,后来它优化了)。


如果是race condition的情况,即线程竞争,那会出现什么情况呢?

比如thread1拿到锁了,然后睡了5s。

这期间,thread2来了。


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

这时候getState()为1,进else if。

判断thread2是否为当前持有锁的线程,当然为false。如果是thread1进来的话,else if就可以进去,然后state就会+1,这就是重入锁的逻辑了。

好,thread2返回false。

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

tryAcquire(arg)取反为true

于是进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

这就是所谓的入队操作。

先看addWaiter

/** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

它用来标记有个结点正以专有模式在等待。

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

首先new一个node。

   Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

然后把tail赋给pred。于是prednull

enq(node);

我们之前知道,一个node的主要内容就是prev,next,和thread。

现在new了一个node维护thread2。

ReentrantLock的原理_第9张图片

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

这就是入队

看Daug Lea怎么说的。

 * <p>To enqueue into a CLH lock, you atomically splice it in as new
     * tail. To dequeue, you just set the head field.
     * <pre>
     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *      +------+       +-----+       +-----+
     * </pre>
     *
     * <p>Insertion into a CLH queue requires only a single atomic
     * operation on "tail", so there is a simple atomic point of
     * demarcation from unqueued to queued. Similarly, dequeuing
     * involves only updating the "head". However, it takes a bit
     * more work for nodes to determine who their successors are,
     * in part to deal with possible cancellation due to timeouts
     * and interrupts.

入队的话,只要在尾巴上粘一个就行了。出队就要更新头了。

我们把封装了thread2的node传进来,进入一个死循环。

tail赋给t,t为null,进if。

new一个空node:

Node() {    // Used to establish initial head or SHARED marker
        }

然后compareAndSetHead,使head指向空node。

然后使tail也指向空node。

 /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

ReentrantLock的原理_第10张图片
第二次循环。

tail不为null,所以t不为null

进else。

第一步,让node(thread2)的prev指向空node。

然后compareAndSetTail。看tail是不是t,当然是啊。如果是的话,就把tail设为node(thread2)(传进来的node)。

然后让t的下一个节点(next)指向node。

ReentrantLock的原理_第11张图片
最后把t返回出去,注意,我们返回的是当前节点的上一个节点(为什么要这么做呢?)。

enq(node)方法结束。队呢排进去了。

然后上一层返回node:

 /**
     * 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;
    }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

然后进acquireQueued方法。

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

又是一个死循环。

首先拿出当前node(thread2)的上一个node,那就是head呗。

 /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

也就是说,封装thread2的node问:“老子是不是第一个排队的?”

唉,是,那我就再去拿一次锁(搞不好thread1已经睡好了并且解锁了呢!)所以tryAcquire(arg)一次。可以成功,成功了就拿到锁了,如果返回false,thread1还没完事,那么

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;

看来是不得不等了。

/**
     * 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.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    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是0。

  * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

于是进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);

把上一个节点的waitStatus换成SIGNAL,也就是-1

然后return 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;
            }

它又去问了一遍现在能不能拿到锁?(thread2看来还是挺不甘心的)。
(这就是所谓的自旋)。

要是还是拿不到,我们又要进shouldParkAfterFailedAcquire

这时waitStatus等于Node.SIGNAL,于是返回true

那就park,停在这儿吧。

   /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

好,此时thread3也来了。

假设thread1还是没有释放锁,那么thread3将经历和thread2一样的逻辑,直到:

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

这时候pred不为null了,所以最后维护好的链表为:

ReentrantLock的原理_第12张图片
排好队之后,进acquireQueued方法。

因为final Node p = node.predecessor();并非head,所以thread3都没有资格去tryAcquire。所以说,自旋只发生在thread2身上,或者说,只发生在队列中的第一个人身上。

接着进入shouldParkAfterFailedAcquire(p, node)

 int ws = pred.waitStatus;

先要拿到thread2的waitStatus,当然是0

接着compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

把thread2的waitStatus设置成-1

然后thread3就去park了。

ReentrantLock的原理_第13张图片
(上图的第3个node的waitStatus不是-0,就是0啊)。

至此为此,有几个问题。

第一:为什么队列要维护一个thread为null的node呢?

第二:为什么thread2要自旋?

第三:为什么要由后面的线程(node)来修改前面的线程(node)的waitStatus?

  • 第一个问题:

我们知道,thread1并没有进入队列。

那如果thread1释放锁,thread2拿到锁队列会变成什么样子呢?

thread1先tryRelease

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

return true

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

拿到head,进入if。

 /**
     * 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;
        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) {
            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,head的waitStatus是-1,compareAndSetWaitStatus(node, ws, 0);将head的waitStatus设置为0。

然后取出thread2那个node,unpark它。

那么之前thread2是在哪里park的呢?

  /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

thread2在这里park

于是thread2继续往下执行。

返回Thread.interrupted();,thread2肯定没有被打断,所以返回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);
        }
    }

这样子就又进入了死循环。

这时候thread2就能拿到锁了。

 if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

然后把thread2的那个node设置为head。

  private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

ReentrantLock的原理_第14张图片
这样就保证了这么一条原则:持有锁的线程不参与排队;为什么呢?因为设计思路是:持有锁的线程是处理业务的线程,它不属于wait queue。

  • 第二个问题:就是为了尽量不要去park,因为park要调用底层函数。如果thread1正好释放了锁,那thread2就可以正好获取锁。至于为什么自旋两次,这就是性能问题。

  • 第三个问题:因为睡着后的线程是动不了的,所以只能由后面的线程来帮助修改waitStatus。如果是自己在睡着之前改waitStatus,可能会遇到exception的情况。


至此,我们讲了reentrantLock的上锁过程。讲了线程交替执行时reentrantLock的简便(没有park和unpark)。讲了线程竞争执行时如果thread1拿到锁,thread2和thread3排队的过程。其中thread2还要自旋两次。thread3就没有自旋了。

讲了等待队列为什么需要虚拟出一个thread==null的node作为第一个node。当thread1释放锁后,thread2持有了锁,本质上它不能算作排队的人,thread2也变成了null。现在thread3是第一个排队的人了。

好。

再来看看这种情况:

thread2通过下面代码释放锁:

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

这时候锁已经是自由状态了。

但是,它还没有去unpark下一个线程。

此时thread3排好了队,并且进入死循环询问锁的情况:

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

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

因为state变成0了,所以进入:

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

h!=t返回true,因为此时队列已经被初始化了,h指向thread2(node),tail指向thread3(node)。(s = h.next) == null返回false

当前线程是来询问锁的线程,是thread3,所以s.thread != Thread.currentThread()返回true

那么整体返回true,于是cas。加锁成功。

想象一下如果不是thread3来询问的,而是thread4来询问的,那么s.thread != Thread.currentThread()就会返回false。如此保证了公平。


暂时结束于此。

你可能感兴趣的:(#,并发)