Java并发编程之ReentrantLock源码(一)

前言

ReentrantLock是基于AQS实现的可重入锁,并且能够实现公平锁和非公平锁。

如何使用

public class Test4 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            System.out.println("获取锁...");
        } finally {
            lock.unlock();
        }
    }
}
复制代码
  • 创建一个ReentrantLock锁对象
  • 调用lock()方法进行加锁
  • 执行业务操作
  • finally代码块中调用unlock()方法保证一定能释放锁

源码解析

ReentrantLock定义了一个抽象静态内部类Sync继承自AbstractQueuedSynchronizer,并且有两个实现类NonfairSyncFairSync,分别是非公平锁和公平锁实现。我们这里以非公平锁为例来介绍其源码实现流程。

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
复制代码
加锁成功
// NonfairSync加锁方法实现
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
复制代码

调用ReentrantLocklock方法进行加锁,实际调用的是NonfairSynclock方法,可以看到通过CAS方式来修改state的值,如果成功了说明加锁就成功了,然后把锁的拥有者标记为当前线程。

加锁失败

如果CAS操作失败,那么就会调用AQS中的acquire方法继续尝试获取锁

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

这里的if语句分为两部分,第一部分tryAcquire方法是在NonfairSync类中,然后调用了Sync类中的nonfairTryAcquire方法,代码如下

// NonfairSync类
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
// Sync类
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;
}
复制代码

上面方法的作用就是再尝试一次看能不能获取到锁,首先获取state值如果是0说明当前没有加锁,所以尝试通过CAS修改state加锁,如果成功就把锁的拥有者线程标记为当前线程然后返回;如果state不为0说明有线程已经加锁,然后判断持有锁的线程是不是当前线程,如果是的话对state1表示一次锁重入,然后返回即可。

再回到acquire方法,如果tryAcquire失败那么会判断第二个条件acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

我们先看下addWaiter方法做了哪些事

// AQS类
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
复制代码

首先根据当前线程和独占锁模式创建了一个nodeif代码块的意思是,如果队列尾节点不为空,就尝试把新建的节点插入到尾结点的后面变成新的尾结点。

如果队列中是空的,就会执行enq方法如下:

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;
            }
        }
    }
}
复制代码

这里是一个for循环会一直重试,首先判断尾节点是不是空,如果是空的说明队列就是空的,所以要进行初始化创建一个空节点同时作为头节点和尾节点。下次循环进入else代码块,尝试把当前线程的节点添加到队尾,成功了就直接返回,失败了就会一直重试直到成功为止。

综上所述,addWaiter方法的作用就是根据当前线程和共享模式创建一个节点,然后添加到队列的尾部。

下一篇文章继续来看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法

猜你喜欢

转载自juejin.im/post/7228943838764154935