ReentrantLock

ReentrantLock

Reentrantlock 的本质就是通过操作 AbstractQueuedSynchronizer 中的state字段,实现加锁解锁和可重入锁的功能, state 为 1 和 0 表示加锁和解锁成功,state > 1 的时候用来记录重入锁的次数;
AQS本身是一个双端链表,每个阻塞的线程对应链表中的一个node节点;
有两个变量需要注意下: state 维护的是整个队列的状态,或者叫锁的状态, waitStatus 是node中的属性,维护的是当前节点或者对应线程在解锁的时候要触发的动作;

waitStatus对应的值以及对应要处理的动作:

状态字段,取值如下:
SIGNAL: 该节点的后继节点当前或即将被阻塞(通过 LockSuport.park()方法),因此当前节点必须在释放或取消时唤醒其后继节点
CANCELLED: 该节点由于超时或中断而被取消。节点永远不会离开此状态。特别是,具有取消节点的线程不会再阻塞。
CONDITION: 该节点当前在条件队列中。它不会被用作同步队列节点,直到被转移,此时状态将被设置为 0。这里使用这个值与其他字段的用途无关,但简化了机制。
PROPAGATE: 应该将 releaseShared 传播到其他节点。这在 doReleaseShared 中为头节点设置,以确保传播继续进行,即使其他操作已介入。
0: 无上述情况
这些值按数字排列以简化使用。非负值表示节点不需要信号。因此,大多数代码只需检查符号,而不必检查具体值。
字段初始化为 0(对于正常的同步节点)或 CONDITION(对于条件节点)。它通过 CAS(或在可能的情况下,不带条件的 volatile 写入)进行修改。

源码阅读:
非公平锁:

ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();

lock():

ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
lock():
public void lock() {
    sync.lock();  
    // abstract void lock(); 抽象实现(对应公平锁和非公平锁)
}

NonfairSync#sync.lock():

final void lock() {
    // 会先执行一次cas操作,如果成功就直接将当前线程配置为锁的拥有者
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    // cas失败就通过aqs等待获取锁    
    else
        acquire(1);
}
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

AbstractQueuedSynchronizer#acquire(1):
这个方法先执行 tryAcquire 尝试获取锁,如果获取失败就执行 addWaiter 将当前node加入到AQS队尾,然后在执行 acquireQueued方法:

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

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire:

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

java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //非重入锁,在执行一次cas操作,也就是说获取锁失败以后,并不是直接就加入队列中了,而是又重新获取了一下
    //因为前一个线程可能在当前线程两次检查中间就释放了锁
    //这样做的目的是因为阻塞和唤醒都需要系统调用,很耗时,尽可能减少这种操作
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //重入锁的逻辑 state++
    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;
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter:
这个是加入队列的方法,逻辑包含两部分,
一种是队列还没有初始化,这时候需要执行enq(node);
另一种是已经初始化,执行compareAndSetTail操作,直接将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;
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#enq:
初始化队列,并将node添加到队尾,这里除了添加了当前node节点,还添加了一个空的head节点,
普通的节点会调用这个构造函数初始化 Node node = new Node(Thread.currentThread(), mode);
head节点直接调用了这个构造函数 new Node();
这个空head节点其实就充当了当前已经获取到锁的那个线程,以为这个队列的初始是在获取锁失败的情况下才会创建的,说明此时锁资源已经被一个线程占用了,可以看下这里 NonfairSync#sync.lock()
这个head节点的waitStatus值为默认值0;

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

此时队列的状态是这样子的:
在这里插入图片描述
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued:
addWaiter中已经初始化队列并且添加了一个node(ThreadA)节点了

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            //判断当前节点的前驱节点是否为head节点,
            //是的话(代表是队列中下一个需要被唤醒的就是当前节点)重新尝试获取锁信息
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 走到这里要不就是前驱节点不是head节点
            //(当前线程还不是下一个要被唤醒的线程,就和排队吃饭一样,下一个打饭的人还不是你,你继续等吧)
            
            // 要不就是我已经是下一个要被唤醒的线程了,但是因为现在是非公平的情况,会有被插队的可能
            // java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire 被这个方法插队
            // 我们都是老实人,多一事不是少一事,我在继续等等吧
            
            // 还有就是前一个线程还没有释放锁,你急了
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire:

接着上面的说,进入这个方法有三种情况: 前驱节点还不是head节点;或者被插队;或者上一个线程还没有释放锁
现在队列中 有一个 head 节点和一个 threadA节点,所以 threadA的前驱节点就是head,比如现在是第三种情况,上一个线程还没有释放锁,这时候 tryAcquire 会失败,这时候会执行下面这个方法中 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);操作,将前驱节点的 waitStatus 设置为 SIGNAL,也就代表着前驱节点释放锁以后,会唤醒当前线程;

如果是被插队,证明上一个执行的线程已经释放锁了,执行了unlock方法,他会将头节点waitStatus < 0 的状态置为 0
这时候当前线程也会执行下面这个方法中 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);操作;(这时候你可能会有疑问,head节点的线程都执行完成了,谁还会唤醒当前这个阻塞的线程啊,这里就要说一下 unlock 方法,他在唤醒的时候都是会从head节点开始,所以不管当前执行的线程是哪一个,释放的锁的时候都会从 head.next 节点开始唤醒 ),所以当插队线程执行完成以后,同样会唤醒我们的当前阻塞线程;

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.
         */
         // 将前驱节点的状态改为 SIGNAL,这样前驱节点就会知道要唤醒他的后继节点
         // 类似向前驱节点注册了一个感兴趣的事件
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt:
这个方法就很简单了,通过 LockSupport.park阻塞当前线程;并返回中断标识;

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

java.util.concurrent.locks.AbstractQueuedSynchronizer#selfInterrupt:
这个方法就是处理中断的,这也是为啥 使用 reentrantLock.lock() 的时候,不需要处理中断异常,因为这里只是改变了中断标识,如果需要对中断进行处理,就需要我们自己在代码中 reentrantLock.lock() ;后面判断中断标识,处理逻辑;

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

公平锁的区别:
java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire:
主要就是这个方法的区别,在cas操作之前执行了 hasQueuedPredecessors 函数,这个函数的作用就是判断队列中是否有元素,有的话你就不能抢锁,也要去排队;就是少了上面插队的情况,世界更加和谐一点;

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

下面说一下unlock的逻辑:
java.util.concurrent.locks.AbstractQueuedSynchronizer#release:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        // 这里就是上面说到的,任何线程释放锁的时候,都会从head节点开始
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

java.util.concurrent.locks.ReentrantLock.Sync#tryRelease:
这个方法的作用就是 判读 state 状态是否为 0,如果为 0 说明 解锁次数和加锁次数平衡,可以解锁;
如果不为 0 ,说明是重入锁,只做递减操作,不会唤醒线程;

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

java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor:
如果能够执行到这里,说明 state 已经被重置为 0 ;
这里就是唤醒线程的逻辑了;

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;
    // 将节点的状态重置为 0 
    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.
     */
     // 因为节点有 CANCELLED 的状态,如果被关闭了,就不需要唤醒当前线程了,跳过
     // 这里采用的是从 tail向前遍历,目的都是一样的
    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.park 对应
        LockSupport.unpark(s.thread);
}

condition.signal() 方法:

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

java.util.concurrent.locks.ReentrantLock.Sync#isHeldExclusively:
判断一下当前这个线程是不是持有锁的线程,不是的持有的,你释放个锤子啊;

protected final boolean isHeldExclusively() {
    // While we must in general read state before owner,
    // we don't need to do so to check if current thread is owner
    return getExclusiveOwnerThread() == Thread.currentThread();
}

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#doSignal:

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#transferForSignal:

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 通过状态维护了多个队列
    // condition中的状态都是 CONDITION
    // 如果状态变成了 0 ,也就是添加到 aqs队列中的初始值
    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).
     */
    // 这里就是将节点从 condition 队列中转移到 aqs 队列的逻辑,重新入队
    Node p = enq(node);
    int ws = p.waitStatus;
    // 这个 p 是当前节点的前驱节点,不是node节点本身
    // 如果前驱节点被关闭 或者 更新 SIGNAL 失败,则直接唤醒当前线程
    // 否则只是修改前驱节点的状态为 SIGNAL,然后当前线程继续阻塞,唤醒的逻辑和前面aqs就一样了
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值