深入理解 ReentrantLock 源码

image

特性

  1. 显示锁,自旋锁,可重入锁
  2. aqs队列锁的实现
  3. 支持多条件唤醒
  4. 支持打断
  5. 支持公平,非公平锁
  6. 可尝试加锁

使用方式

ReentrantLock lock = new ReentrantLock();
try {
    // 加锁
    lock.lock();
    // do something
} finally {
    // 在finally中解锁,避免出现异常导致 没释放锁而死锁
    lock.unlock();
}

源码

1. lock() 公平锁

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

AbstractQueuedSynchronizer. acquire(1)

public final void acquire(int arg) {
    // !tryAcquire(arg) 尝试加锁
    // acquireQueued(addWaiter(Node.EXCLUSIVE) 加入队列
    //  selfInterrupt 自我打断
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}   
tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 判断当前锁的状态, 0 代表没有人持有
    int c = getState();
    if (c == 0) {
        // 判断队列中是否有线程在等待,如果没有则cas改变锁的状态
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // cas成功,设置当前线程为持有锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 有线程占有锁,判断持有线程是否有是自身,是的话重入
    else if (current == getExclusiveOwnerThread()) {
        // 锁状态 + 1
        int nextc = c + acquires;
        // 锁状态 < 0 ? 加锁次数太多,导致整形溢出?
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置锁的状态
        setState(nextc);
        return true;
    }
    // 抢锁失败
    return false;
}
hasQueuedPredecessors()

判断队列中是否有线程在排队等锁

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;
    // 头结点一般都是 空结点
    // 头结点 != 尾结点
    // 头结点下的next为空  或者 头结点的后一个结点的线程不是当前线程
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
acquireQueued(final Node node, int arg) : 入队睡眠

如果当前锁被其他线程持有(非自身重入), 或者队列中有线程在排队,则加锁失败,需要创建Node节点,入队(如果队列还没有初始化则会初始化队列),并且入队之后,会判断自己Node的前驱节点是不是head,是的话,会自旋拿一次锁。

自旋拿锁成功会把自己做为新的头节点,空Node

addWaiter(Node mode) : 创建Node节点
// 创建线程为当前线程的Node对象
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节点的前节点设置为当前节点
    node.prev = pred;
    // 将新创建的Node节点设置为新的尾节点
    if (compareAndSetTail(pred, node)) {
        // 之前的尾节点的后驱节点设置为 新创建的Node对象
        pred.next = node;
                // 返回
        return node;
    }
}
// 如果尾节点为空,初始化队列
enq(node);
return node;
enq(Node mode) : 入队

如果队列还没有初始化,就会新建队列,队列的头结点和尾节点都为空的Node

将当前线程的Node节点接在之前的尾节点的后面,并设置成新的尾节点

private Node enq(final Node node) {
    for (;;) {
        // 尾节点
        Node t = tail;
        // 第一次进来,如果为空,说明队列还没有初始化
        // 第二次进来就不为空了
        if (t == null) { // Must initialize
            // 第一次进来,创建空的Node,将头节点和尾节点都设置成这个空的Node
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 不为空走这里,传进来的Node的前驱设置成之前的尾节点
            node.prev = t;
            // 传进来的Node成为新的尾节点,
            if (compareAndSetTail(t, node)) {
                // 之前的尾节点的后驱节点设置为传进来的Node
                t.next = node;
                return t;
            }
        }
    }
}
acquireQueued :自旋,失败后睡眠
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);
    }
}
shouldParkAfterFailedAcquire(Node pred, Node node)

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;

更改前驱节点的状态 :

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 判断前驱Node,是不是唤醒状态,是的话直接返回true,让当前Node去睡眠
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    // 如果是> 0,说明是取消状态
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 一直往前查找,知道找到非取消的状态的节点,作为当前Node的前驱
        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.
         */
        // 如果是0, cas将前驱Node的状态改为Node.SIGNAL, 外面第二次循环,直接返回true,让当前节点睡眠
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
parkAndCheckInterrupt()

阻塞当前线程,直到被打断,打断后会清空打断标记,在下一次循环中继续进来阻塞。造成一种无法打断的假象。

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

加锁流程

  1. 当锁为无锁状态时 :

    image
  2. 线程t1来加锁 : 持有锁的线程改成t1,锁的状态改为1:被占有状态

    image
  3. 线程t1没有释放锁,线程t2来抢锁, t2会创建队列,并且排队

    image
  1. 线程t1没有释放锁,线程t2入队,线程t3来抢锁
image

lock() : 非公平锁

非公平锁的加锁流程与公平锁不同的是,非公平锁在加锁的时候,不会判断队列中是否有线程在排队,直接cas加锁

final void lock() {
    // cas改锁的状态
    if (compareAndSetState(0, 1))
        // 修改成功直接抢占锁,当锁的线程改为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 尝试加锁
        acquire(1);
}

tryAcquire() & nonfairTryAcquire()

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) {
        // 公平锁这里会判断队列是否有人在排序,非公平锁直接cas上手
        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;
}

再后面抢不到锁,进入队列之后和公平锁的逻辑是一模一样的。

公平和非公平之前在于 入队之前尝试加锁,看不看队列中有无线程排队,如果加锁失败,入队了,那么就再没有公平和非公平之分

2. unlock()

    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        // 解锁, state一直减到0,锁才会被完全释放
        if (tryRelease(arg)) {
            Node h = head;
            // 当锁完全释放后
            if (h != null && h.waitStatus != 0)
                // 叫醒后面排队的线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease

释放锁,state - 1,减到0,锁才会被完全释放,当前持有锁线程设置为null

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 状态为0,锁才完全释放, 重入时,state会被累加到 > 1,需要解锁多次
    if (c == 0) {
        // 完全释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

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

解锁流程

image
image

3.lockInterruptibly()

和lock方法不同的是,该加锁方法会响应打断异常

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    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;
            }
            if (shouldParkAfterFailedAcquire(p, node) && 
                parkAndCheckInterrupt())
                // 如果再睡眠过程中被打断,会抛出异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

4.Condition 条件唤醒

await() : 等待

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 创建condition等待队列
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // 睡眠
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
addConditionWaiter()

如果尾节点为空,新建一个Node节点(持有当前线程),state为-2, 头节点和尾节点都等于新建的Node节点

如果尾节点不为空,新建一个Node节点(持有当前线程),接在尾节点的后面,并作为新的尾节点

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

signal() : 唤醒

唤醒条件队列的第一个Node

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 头节点
    Node first = firstWaiter;
    if (first != null)
        // 唤醒
        doSignal(first);
}
doSignal
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
transferForSignal(Node node)
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 将当前节点从条件队列加入到 等待队列中
    // 返回是上一个节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // 然后将上一个节点的状态改为SIGNAL -1
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果cas失败,unpark
        LockSupport.unpark(node.thread);
    return true;
}

doSignalAll : 唤醒所有

将条件队列中所有的Node按顺序 从头到尾加到等待队列中

public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    // 无限循环,直到条件队列中没有元素
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        // 从头到尾按顺序,加到等待队列中
        transferForSignal(first);
        first = next;
    } while (first != null);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容