ReentrantLock源码解读,看完就是干货

403 阅读7分钟

ReentrantLock源码解读

前情提要阅读前需对公平锁非公平锁可重入锁非可重入锁独占锁共享锁。有一定的了解。各种锁的概念及介绍

1、ReentrantLock的类结构

他的结构非常简单,如图

graph BT
ReentrantLock --implements--> Lock
ReentrantLock --implements--> Serializable

ReentrantLock实现的锁又可以分为两类,分别是公平锁非公平锁,分别由ReentrantLock类中的两个内部类FairSyncNonfairSync来实现。FiarSync和NonfairSync均继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer(AQS)类,所以ReentrantLock最终是依靠AQS来实现锁的。

公平锁和非公平锁继承图,因为他们的顶层父类都是AbstractOwnableSynchronizer,所以他们基于AQS都是独享锁,锁概念请查看

graph BT
FairSync内部类 --继承-->  Sync --继承--> AbstractQueuedSynchronizer --继承--> AbstractOwnableSynchronizer
NonfairSync内部类 --继承-->  Sync 

2、ReentrantLock公平锁源码解读

2.1 构造方法源码

ReentrantLock的构造方法如下

/**
  *  无参的构造方法默认构建一非公平锁
  */
public ReentrantLock() {
        sync = new NonfairSync();
    }
/**
  *  有参的构造,
  *  根据出传入的参数来构建一个公平锁或非公平锁
  */
public ReentrantLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
    }
​
​
  • 当调用lock.lock()时,会调用到FairSync.lock()方法,FairSync.lock()方法会调用AQS中的acquire()方法。在AQS的acquire()方法中会先调用子类的tryAcquire()方法,此时由于我们创建的是公平锁,所以会调用FairSync类中的tryAcquire()方法

2.2 公平锁创建以及加锁

// 该锁现在是独享锁,公平锁,可重入锁 
ReentrantLock lock = new ReentrantLock(true);
    try{
        lock.lock(); // 检查是否可以得到锁会调用子类的实现的方法tryAcquire来获取 
        System.out.println("Hello World");
    }finally {
        lock.unlock(); //解锁
    }
  • 创建的是公平锁,所以这个时候调用的是FailSync的lock()方法。
static final class FairSync extends Sync {
​
    final void lock() {
        // 调用AQS类acquire()获取同步状态(获取锁)
        acquire(1);
    }
}

2.3 AQS中acquire()方法源码解读

  • 在FailSync的lock()方法中就会调用到AQS的模板方法:acquire()该方法的源码以及解读如下。最终的加锁逻辑就是在acquire()方法中实现的。acquire()的源码如下:

调用子类的具体实现-tryAcquire()方法是尝试获取同步状态,如果该方法返回true,表示获取同步状态成功;返回false表示获取同步状态失败
           第1种情况: 当tryAcquire()返回true时。
    这种情况表示线程获取锁成功,acquire()方法会直接结束并返回。
第2中情况: 当tryAcquire()返回false时,表示线程没有获取到锁,这时需要将线程加入到同步队列。

执行addWaiter(Node.EXCLUSIVE)方法(Node.EXCLUSIVE是独占锁;Node.SHARED是共享锁),将当前线程放入到执行队列中。

在独享锁中,addWaiter()的参数是null,addWaiter()方法返回的是当前线程的节点。

再调用acquireQueued()方法,在这个方法中,会先判断当前线程代表的节点是不是第二个节点(为什么是第二个呢?因为第一个是目前在执行的线程,所以第二个需要去阻塞获取锁,以此来执行),如果是就会尝试获取锁,如果获取不到锁,线程就会被阻塞;如果获取到锁,就会返回。

acquireQueued()方法如果返回的是true,表示线程是被中断后醒来的,此时if的条件判断成功,就会执行selfInterrupt()方法,该方法的作用就是将当前线程的中断标识位设置为中断状态。 如果acquireQueued()方法返回的是false,表示线程不是被中断后醒来的,是正常唤醒,此时if的条件判断不会成功。acquire()方法执行结束

总结:只有当线程获取到锁时,acquire()方法才会结束;如果线程没有获取到锁,那么它就会一直阻塞在acquireQueued()方法中,那么acquire()方法就一直不结束。

  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 调用selfInterrupt()方法重新设置中断标识
            selfInterrupt();
  }

2.3.1 FairSync(公平锁)的tryAcquire方法解读

公平锁得到锁的方法如下: tryAcquire 返回ture表示得到的锁,在AQS中维护了一个工作的队列,在获得锁的同时我们会发现有一个setExclusiveOwnerThread()方法,该方法用于设置改锁的独享性。

 protected final boolean tryAcquire(int acquires) {
     // 得到当前的线程
            final Thread current = Thread.currentThread();
     // 得到锁的状态
            int c = getState();
     // c==0 表示没有其他人得到锁
            if (c == 0) {
                // 因FairSync是公平锁,所以不能插队,需要判断是否有其他线程在队列
                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;
        }
    }
// ----------------------------------------华丽的分割线---------------
// 内部类FairSync继承内部类Sync,内部类Sync继承AbstractQueuedSynchronizer
  public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
    // 返回false说明没有线程排队
        return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
    }

2.3.2 FairSync(公平锁)的addWaiter(Node.EXCLUSIVE)解读

返回当前需要执行的节点。

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
  // 利用CAS插入链表的最后一个节点
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
  // 用作初始化执行线程的队列
        enq(node);
        return node;
    }
// ----------------------------------------华丽的分割线---------------
 private Node enq(final Node node) {
        for (;;) {
          // 最后一个节点=null 初始化一个,并让tail=head
            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;
                }
            }
        }
    }

2.3.3 FairSync(公平锁)的acquireQueued()源码解读

  • 在执行完addWaiter()方法后,就会执行acquireQueued()方法。该方法的作用就是让线程以不间断的方式获取锁,如果获取不到,就会一直阻塞在这个方法里面,直到获取到锁,才会从该方法里面返回。
  • Anchor在这里介绍线程的三个方法。interrupt 发送线程中断指令,具体什么时候中断看情况。isInterrupted检查中断状态,但不清除中断信息(只是检测是否被中断)。interrupted检查线程是否中断,且返回后会重置中断状态为未中断。
// 如果在等待时中断,则为true
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;
                }
              // waitStatus >0 是已经停止中止的线程,不需要在队列中排队
              // 只有waitStatus = -1(SIGNAL)线程才会被唤醒, 
              // parkAndCheckInterrupt停止当前线程并,检测中断状态,并清除中断状态等待释放锁的时候唤醒线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3、unlock()释放锁源码解读

释放锁的流程

1、ReentrantLockunlock方法会调用AQS的release() 方法进行实现

2、在release中会先尝试释放锁(因为是可冲入锁,所以需要尝试释放,不是一定会释放)

3、释放失败,就返回,释放成功就唤醒队列单中第二个需要唤醒的线程继续执行

  public final boolean release(int arg) {
    // 调用ReentrantLock中内部类Sync的tryRelease来实现锁的释放 
        if (tryRelease(arg)) {
            Node h = head;
            // 唤醒队列的线程waitStatus != 0 说明有队列,唤醒队列中的线程
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

3.1 tryRelease源码解读

在释放锁的过程当中,因为ReentrantLock是可重入锁和独享锁,所以释放锁时还需要释放掉独享锁锁存的线程信息。

// 返回true说明已经释放锁,不为0说明当前线程的其他方法还持有锁
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
                        // 不是当前线程,释放锁失败
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
                    // c==0 说明已经完全释放掉锁,释放掉独享锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

3.2 unparkSuccessor源码解读

点击跳转到唤醒后线程执行的位置

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
            // 清除waitStatus的信息
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        // 队列的下一个节点的waitStatus说明线程不需要被唤醒,
            // 剔除所有不需要被唤醒的线程
        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);
    }

3、小结

经过上面的源码,我们可以看到,ReentrantLock是一个悲观锁、可重入锁、独享锁,并可以通过构造参数来指定是公平锁还是非公平锁的一个具体实现,与synchronized相比,它更具有可塑性,并且它可以通过waitStatus的状态来控制当前线程是否继续等待锁。ReentrantLock的公平锁源码已经被我们全部拔出来了,请各位看官老爷看的时候并结合自己的理解debug调试一下加深一下理解。

下面我们来看一下ReentrantLock的非公平锁源码他和公平锁的源码几乎相差无几。

4、ReentrantLock非公平锁源码解读

4.1 非公平锁的创建以及加锁

传入的参数为false创建的内部类对象是NonfairSync。

// 该锁现在是独享锁,非公平锁,可重入锁 
ReentrantLock lock = new ReentrantLock(false);
    try{
        lock.lock(); // 检查是否可以得到锁会调用子类的实现的方法tryAcquire来获取 
        System.out.println("Hello World");
    }finally {
        lock.unlock(); //解锁
    }

公平锁调用的是NonfairSync的lock方法,通过查看源码可以看到非公平锁加锁的时候会先通过CAS先查看自己是否可以获取锁。

如果获取锁则将线程加入到自己的独享锁中保存当前线程。

如果不能获取锁,则像公平锁一样去调用父类的acquire(1) 将锁加入线程队列当中。但是在获取锁是调用的是NonfairSynctryAcquire方法,然后去调用父类的Sync来尝试获取锁。

final void lock() {
     if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
     else
        acquire(1);
}
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}

4.2 非公平锁获取锁的源码解读

通过下面的源码,我们可以看到非公平锁的源码和公平锁的源码只相差了一个 !hasQueuedPredecessors() 方法,该方法是判断当前线程是不是队列当中的第一个,也就是可以插队来执行。

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

Synchronized和ReentrantLock 的区别

synchronizedReentrantLock
锁实现机制对象头监视器模式依赖AQS
灵活性不灵活支持响应中断、超时、超市获取锁
释放锁的形式自动释放显示调用unlock()
支持锁类型非公平锁公平锁&非公平锁
条件队列单条件队列多个条件队列
是否可重入支持支持

由于作者水平有限, 欢迎大家能够反馈指正文章中错误不正确的地方, 感谢 🙏

由于作者水平有限, 欢迎大家能够反馈指正文章中错误不正确的地方, 感谢 🙏

由于作者水平有限, 欢迎大家能够反馈指正文章中错误不正确的地方, 感谢 🙏

参考文章

AQS中waitStatus的含义

线程中断

文章三

文章四

美团的AQS原

万字详解AQS