美文网首页
JUC原理之ReentrantLock

JUC原理之ReentrantLock

作者: AlienPaul | 来源:发表于2020-03-17 10:55 被阅读0次

概念

ReentrantLock是Java JUC包中最为重要的一员。中文翻译为可重入锁,意思是允许一个已经获得锁的线程再次获得锁。如果一个线程获得了n次锁,那么释放锁的时候也必须要释放n次,才会恢复到无锁的状态。ReentrantLock是独占锁,同一时刻只能有一个线程获得锁,其他请求锁得到线程都会阻塞。ReentrantLock同时支持公平锁和非公平锁。默认构造函数创建出的ReentrantLock为非公平锁,可以通过new ReentrantLock(true)创建出公平锁。

使用方法

以一段示例代码说明ReentrantLock的使用方法:

class SomeClass {
    // 创建一个可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void method() {
        lock.lock();  // 加锁,如果锁被其他线程获取会阻塞
        try {
            // ... 其他逻辑
        } finally {
            // 务必在finally块释放锁
            lock.unlock()
        }
   }
}

需要特别注意的是,ReentrantLock必须在finally代码块中unlock。否则一旦代码运行抛出异常会造成锁永远不被释放。

公平锁实现方式

ReentrantLock中有一个Sync类,Sync类是AQS的一个实现。根据公平和非公平的要求,Sync又衍生出两个子类:FairSyncNonfairSyncFairSync是公平锁的实现方式。它的代码如下所示:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    // 公平锁加锁方法入口
    final void lock() {
        // 直接走tryAcquire,如果tryAcquire失败,进入排队流程
        // 这里1的意思是重入锁深度加1
        // 如果同一个线程反复lock,重入锁深度就一直增加
        // 相反,锁释放的时候重入锁深度减小,直到为0的时候,线程的锁释放
        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();
        // 如果同步状态为0,说明没有线程持有锁
        if (c == 0) {
            // 如果队列中没有其他线程等待,CAS尝试设置同步状态
            // 这里是公平锁实现的关键
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 如果成功,设置独占线程为当前线程,返回true
                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;
        }
        // 其他情况,说明无法获得锁,返回false
        return false;
    }
}

AQS的hasQueuedPredecessors方法用于判断是否有其他线程在排队等待或者其他线程获取到锁尚未释放。它的逻辑如下:

  • 如果队列刚初始化,tail和head都是new Node(),说明没有线程在等待,返回false。
  • 或者说队列中tail和head都是同一个包含某个thread的node。说明这个线程获取到锁正在执行,或者已经释放锁。但是后续没有线程在排队,此时返回false。
  • (s = h.next) == null说明tail和head被初始化完成但是中间有新节点加入。另一个线程刚好执行完下面方法(1)这一行,此时t(即head)的next节点为null。考虑到这种情况,队列中即将有其他线程排队,因此需要返回true。
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)) { //(1)
                t.next = node;
                return t;
            }
        }
    }
}
  • 如果队列中有线程在排队,并且head的后置节点线程不是当前运行的线程,返回true。
  • 如果队列中有线程在排队,并且head的后置节点线程是当前运行的线程,返回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;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

非公平锁的实现方式

非公平的ReentrantLock具有如下特点:

  1. 只有不在队列中的线程和队列中紧随head的线程获取锁的时候才可能发生锁的不公平抢占。
  2. 如果线程释放了锁,如果不考虑情况1的抢占,队列中排队线程获得锁的顺序仍为排队顺序。

非公平锁使用Sync的另外一个实现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() {
        // 这里是非公平方式,如果CAS设置state成功,可以直接获得锁
        // 这是一个非公平锁可能抢占的地方
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 否则进入正常获取锁逻辑
            acquire(1);
    }

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

继续看nonfairTryAcquire方法的实现:

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果没有线程持有锁
    if (c == 0) {
        // CAS设置同步状态,成功的话当前线程获得锁
        // 这时没有判断队列中是否有线程在等待
        // 如果上一个线程释放锁,下一个排队的线程还没有来得及获得锁,这时候state为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;
    }
    // 其他情况获取锁失败,返回false
    return false;
}

本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。

相关文章

网友评论

      本文标题:JUC原理之ReentrantLock

      本文链接:https://www.haomeiwen.com/subject/tyuzehtx.html