通过源码看Java中ReentrantLock类

403 阅读3分钟

本文中涉及的源码,来自JDK8。

ReentrantLock,独占式锁,支持一个线程对资源的重复加锁,再次加锁时不会阻塞自己(synchronized关键字隐式地支持锁重入)。此外,它还支持获取锁时选择公平或非公平模式。

那么,何为公平锁?指在绝对时间上,先申请锁的请求一定先被满足,那么这个锁是公平的;反之,是不公平的。可见,获取公平锁时,按照FIFO原则,在同步队列中等待时间最长的线程最先获取锁。

1 创建ReentrantLock

无参构造器默认使用非公平锁。入参为true时使用公平锁。 image.png image.png

2 同步器实现

在ReentrantLock中,内部类Sync继承AbstractQueuedSynchronizer,作为同步组件。它有两个子类:NonfairSync、FairSync,分别是非公平锁、公平锁的实现。 image.png image.png

3 加锁

分别来看公平、非公平两种模式:

  • 非公平模式时,lock()时先CAS尝试获取锁,如果成功则退出,不用进入同步队列等待。 image.png
  • 公平模式时,lock()时直接走AQS的acquire(),如果tryAcquire(arg)失败,线程将进入同步队列等待。 image.png

4 锁重入

4.1 加锁

锁重入的实现在ReentrantLock.Sync#nonfairTryAcquire方法中,关键在于:当同步状态不可获取时,锁需要能够识别当前线程是否是锁持有者。 image.png 锁对象如何记录锁持有者呢?AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer类,后者的exclusiveOwnerThread属性,能够记录持有独占式锁的线程。 image.png 当线程重复获得锁时,对state计数自增即可。

4.2 释放锁

tryRelease(int releases)方法中:

  • 当线程对锁重入了n次后,前n-1次释放锁时,state计数自减,返回false,并未真正失去锁;
  • 第n次释放锁时,计数等于0,方法返回true,才会唤醒阻塞中的后继线程。 image.png image.png

5 公平与非公平

区别在于:

  • 公平模式时,申请资源的(多个)新线程到来时,即使同步状态可被获取,如果队列中有等候的线程,当前线程也不能尝试去获取,必须将添加到队尾,排队等候。 image.png
  • 非公平模式时,(多个)新线程申请资源时,可以尝试争抢资源;成功时不用入队,失败时才被添加到队尾排队。 image.png

使用非公平锁时,如果一直有新线程到来,可能导致入队很早的线程,很久不能获取到锁,造成饥饿。但是,它能够减小线程切换次数(新线程有很大概率不用入队阻塞),因而非公平锁有更大的吞吐量

公平锁必须按照FIFO原则,依次排队来获取锁。因此,公平锁往往没有非公平锁的效率高。但是,它可以减少饥饿发生的概率,等待越久的线程越是优先获取到锁。

6 使用ReentrantLock来避免死锁

常常有这样的场景:一个线程任务需要同时获得多把锁时,才能顺利执行。

使用synchronized时,很容易造成死锁。如下代码,t1线程在获得lock1后,阻塞在获取lock2;正好有其他线程获取了lock2,阻塞在获取lock1。此时,发生了死锁。

    Object lock1 = new Object();
    Object lock2 = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (lock1) {
            log.debug("lock1");
            sleep(1);
            synchronized (lock2) {
                log.debug("lock2");
                // do something
            }
        }
    }, "t1");

如果线程获取更多锁失败时,能够自动释放已经获得的锁,将避免可能发生的死锁问题。

ReentrantLock的tryLock()方法是非阻塞的,使用它很容易实现这个功能。

	ReentrantLock lock1 = new ReentrantLock();
	ReentrantLock lock2 = new ReentrantLock();

    Thread t1 = new Thread(() -> {
        while (true) {
            if (lock1.tryLock()) {
                log.info("t1 get lock1");
                try {
                    // tryLock是非阻塞的
                    if (lock2.tryLock()) {
                        log.info("t1 get lock2");
                        try {
                            log.info("t1 do something...");
                            break;
                        } catch (Exception e) {
                        } finally {
                            lock2.unlock();
                        }
                    }
                } catch (Exception e) {
                } finally {
                    lock1.unlock();
                }
            }
        }
    }, "t1");