ReentrantLock源码分析

并发源码分析篇:

  • ReentrantLock源码分析
  • ReentrantReadWriteLock源码分析
  • Condition源码分析
  • CountDownLatch源码分析

首先我们用ReentrantLock编写了下面这段代码

package com.ltf.study.concurennt;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
    private int num;
    private static final Lock lock = new ReentrantLock();
    public void incrNum() {
        try {
            lock.lock();
            num++;
        } finally {
            lock.unlock();
        }
    }
}

在这里假设有三个线程A、B、C同时进入这段代码,如何在不加锁的情况下num的值有可能不等于3。但是加上锁之后就能保证线程之间有顺序性的进入这段代码,通过源码发现,Lock锁是通过一个双向队列将为获取的锁的线程阻塞,并且获取锁的线程执行完代码后调用unlock方法释放锁并且唤醒队列中的下一个线程来竞争锁。这里假设A先抢占到锁

首先看lock方法的实现

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public void lock() {
        sync.lock();
    }   
    public class ReentrantLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = 7373984872572414699L;
        /** Synchronizer providing all implementation mechanics */
        private final Sync sync;

img

继承体系图

从这里可以看出,ReentrantLock.lock()方法实际上是调用了Sync的子类NonfairSync(非公平锁)的lock方法。而Sync同时继承了AbstractQueuedSynchronizer同步器(简称AQS),锁的大部分代码实现都是基于这个同步器来实现的。

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

lock方法首先会走compareAndSetState这个方法,而该方法实际上就是同步器中的一个方法,这个方法最终会调用一个native底层方法,我们不需要关注,只需知道该方法实际就是类似于乐观锁的操作,比较版本号是否与预期版本号相同,如果相同设置给定值并返回成功标识,如果不同则返回失败标识。compareAndSetState方法则是判断AbstractQueuedSynchronizer中的state值是否为0,如果为0,就修改为1,并返回true。很显然state初始值就是0,所以state值被修改成了1,并且调用同步器父类AbstractOwnableSynchronizer的方法setExclusiveOwnerThread(Thread.currentThread())方法将当前独占锁线程设置为当前线程,而当前线程也就是线程A,线程A可以正常执行逻辑。此时,线程B也调用了lock方法,执行compareAndSetState方法肯定是失败的,因为此时的state为1,所以线程B将执行acquire方法。

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

继续跟踪代码发现又会走tryAcquire方法,该方法是同步器中的一个抽象方法,具体实现由子类实现,而这里子类则是NonfairSync,最终调用的是父类SyncnonfairTryAcquire方法。这段代码也是说明ReentrantLock是重入锁,这个方法的意思就是在取尝试重试获取一次锁,因为有可能其他占有锁的线程很快就释放锁了,这里在次尝试了一次,如果获取到了,就直接返回true。如果失败继续会判断当前独占锁的线程和当前线程是否为同一个线程(重入锁的实现),如果是,将state设置为state+1,并且放回true,而这里他们不相等,当前独占锁的线程为线程A,当前线程为B,所以结构会返回false。!tryAcquire(arg)则为true,所以会继续执行同步器的addWaiter(Node.EXCLUSIVE)方法


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;
    static final int PROPAGATE = -3;
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
img

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

首先是创建一个node节点并且将当前线程(线程B)赋值给thread字段,同步器中存在tail,head两个成员变量用来记录链表中的头和尾节点,此时将tail赋值给pred,tail是null,所以代码将执行enq(node)

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

接着会执行compareAndSetHead方法,从名字上可以看出该方法就是将一个刚创建出来的Node节点设置为头节点,并且tail尾部节点也指向这个节点,结构如下图

img

接着继续循环执行死循环里面的代码,此时tail不等于null,走else语句代码,将传进来的Node结点(thread=B的节点)的pre前节点指向tail节点,并且将传进来的Node节点设置为tail节点,并且将上一个tail节点的next节点设置为当前传进来的节点,此时结构图变成如下

img

到此addWaiter(Node.EXCLUSIVE)方法执行完成,并返回了节点NodeB,将继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

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

继续分析acquireQueued方法的实现,同样又是个死循环,首先执行node.predecessor()方法返回传入的node节点的pre节点,而pre节点就是tmp节点并且是head节点,所以继续执行tryAcquire方法,尝试获取锁肯定是失败的,因为线程A占据着锁,所以代码会执行shouldParkAfterFailedAcquire(p, node)

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

很显然pred.waitStatus = 0,会执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法,目的就是将tmp节点的waitStatus设置为-1,所以此时的首节点tmp的waitStatus=-1。shouldParkAfterFailedAcquire(p, node)返回的false,继续会循环执行到这个方法,而此时的waitStatus=-1,所有直接返回true,继续执行parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

该方法就是阻塞当前线程并且下次被唤醒的时候返回是否被中断标识。到此,线程B的运行到此为止,线程B被挂起,只有等到线程A执行完成后并且释放锁同时唤醒线程B才能继续运行。

同理线程C调用lock方法同样是执行线程B的逻辑,将上一个节点(NodeB)的waitStatus设置为-1,next节点设置为自身构成的节点(NodeC),并且将自己的pre节点指向NodeB,tail节点也指向了NodeC最终的列表结构如下

img

现在NodeB与NodeC都被挂起了,需要线程A执行完之后调用unlock方法来唤醒下一个节点

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
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;
}

unlock方法最后会执行同步器中的release方法,并且通过调用ReentrantLock类中的tryRelease方法尝试释放锁。

tryRelease方法主要做了这几件事

1、如果释放的线程跟独占线程不是同一个,抛异常

2、如果当前state值减1等于0的话表示锁释放完成,把当前独占线程设置为null并把state值设置为0,返回true

这里为什么state-1会不等于0呢,因为这是重入锁,同一个线程可以lock两次,所以必须得释放两次才是真正的完全释放了锁。

我们的程序中只调用了1次lock方法,所以会执行步骤2,当前的state值为0,独占线程为null,结果返回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;
}
img

拿这之前图继续来看,tryRelease为true, 此时的head为tmp, tmp != null && tmp.waitStatus != 0 也会为true,就会继续执行unparkSuccessor方法

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为tmp,所以会继续执行compareAndSetWaitStatus方法将tmp的waitStatus改为0

tmp下个节点就是NodeB,所以会继续执行LockSupport.unpark(s.thread),s.thread就是线程B,将线程B唤醒了。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
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);
    }
}
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

线程B唤醒后将继续从上次挂起的地方继续执行,也就是执行Thread.interrupted()方法,如果线程被中断过,将interruped标识设为true,继续for循环逻辑,而此时p == head && tryAcquire(arg)为true,执行setHead方法,setHead方法将NodeB设置为头节点,并且thread=null,pre前置节点也为null,这里因为线程B已经唤醒,存储的thread信息已经没作用,所以将其设置为null。****tmp节点的next节点也设置为null,并且返回interrupted标识。此时的链表结构变为

img

此时tmp头节点没有引用了,会被回收。当前的头节点成了NodeB,所以NodeB释放锁之后就会唤醒NodeC做同样的操作

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

最后方法就执行完成了,如果线程被中断过,就会响应中断,调用中断方法。

公平锁和非公平锁

看到上面很多人估计有疑问那这样链表中的线程应该都是FiFo的形式啊,是公平的啊,为什么会说成非公平锁。实际上进入队列中挂起的线程确实是公平的,当时别忘了刚进来的时候是调用tryAcquire方法,如果获取到锁了就不会进入链表中,也不会被挂起,而公平和非公平锁的tryAcquire就一个地方不同,公平锁多了个hasQueuedPredecessors方法,公平锁会判断链表中是否有其他线程在等待,如果有,就会把自己也加入到链表末尾,而非公平锁没有这个判断,直接是尝试获取锁,而正当锁被释放,有一个新的线程调用了lock方法这就会与链表中被唤醒的线程形成竞争关系,所以就成了非公平。

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

总结

分析了ReentrantLock源码实则就是在分析AQS同步器源码,包括Java.util.Concureent并发包下的大部分工具也是基于这个同步器实现的。

ReentrantLock锁的实现步骤可以理解为如下

1、未竞争到锁的线程将会被CAS为一个链表结构并且被挂起。

2、竞争到锁的线程执行完后释放锁并且将唤醒链表中的下一个节点。

3、被唤醒的节点将从被挂起的地方继续执行逻辑。

看到这里,我们也知道了Synchronized与lock的区别了,

1、lock锁是juc报下的工具类,使用更灵活,可以主动释放锁

2、lock锁实现了公平和非公平锁,synchronized是非公平锁

下一篇:ReentrantReadWriteLock源码分析https://www.jianshu.com/p/e09d3d87f7a3

你可能感兴趣的:(ReentrantLock源码分析)