ReentrantLock源码解析

  • Post author:
  • Post category:其他


ReentrantLock

ReentrantLock是JDK内置的显示锁,相对于隐式锁synchronized,ReentrantLock提供了比synchronized更精细化的锁控制。通过情况下,我们会这样使用显示锁……

lock.lock();
try {
  //do something
} finally {
   lock.unlock(); 
}

ReentrantLock是可重入锁,意思是当线程A得到了锁后,还可以继续拿到该锁。

ReentrantLock是独占锁,同时只能有一个线程获取该锁。

ReentrantLock和AbstractQueuedSynchronizer的关系

ReentrantLock其实是通过内部类代理实现了锁的功能,内部类继承自AbstractQueuedSynchronizer,后面会简写为AQS,锁的核心实现都来自于AQS。AQS中的tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared都抛出异常,子类如果需要,实现这几个方法即可。

先来简单看下ReentrantLock的声明:

public class ReentrantLock implements Lock, java.io.Serializable {
   
    //同步器的实现,ReentrantLock都是通过sync代理AQS的
    private final Sync sync;

    //字段……
    //方法……

    //Sync继承了AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //
    }
    //非公平锁
    static final class NonfairSync extends Sync {
    }
    //公平锁
    static final class FairSync extends Sync {
    }
}

ReentrantLock实现了Lock接口,锁的功能是通过内部静态类Sync、NonfairSync、FairSync实现的。

公平锁和非公平锁

公平锁和非公平锁的区别在于获取锁操作过程的差异,后面讲到代码时还会详细介绍,这也是锁的重点,这里简单说明下。

公平锁:检查当前线程的前置结点是否是头结点,如果不是则会继续排队等待,否则获取锁。这种获取锁的方式可以保证队列的FIFO特性,先排队的线程先获取锁,公平地获取锁。但是这种方式效率不高,假如头结点的线程释放了锁,还要去唤醒后一个线程去获取锁,线程上下文切换,成本较高。下图是公平锁的获取示意图:

这里写图片描述


图1:公平锁的获取

非公平锁:获取锁的线程首先去尝试获取锁,如果没有获取成功则会去等待队列排队等待。这种方式首先去尝试获取锁。假如头结点的线程释放了锁,若此时另一个线程恰好去获取非公平锁,那么该线程可以竞争到该锁,免去了释放锁的线程去唤醒后续线程从而引起线程上下文切换。下图是非公平锁的获取示意图:

这里写图片描述


图2:非公平锁的获取

看下ReentrantLock的构造函数:

    //默认构造函数创建非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //通过参数指定创建公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock默认创建的是非公平锁,这是因为非公平锁在实际使用中效率更高。另外还可以通过ReentrantLock提供的带参数的构造函数指定创建公平锁还是非公平锁。

非公平锁的lock方法

前面说到,我们一般会通过调用ReentantLock的lock方法去获取锁,先看下lock的源码:

    public void lock() {
        sync.lock();
    }

lock方法的源码非常简单,锁的功能通过sync代理了。当我们执行如下代码时,将会创建一个非公平锁:

//创建一个非公平锁
Lock lock = new ReentrantLock();

非公平锁是通过内部类NoneFairSync实现的,ReentrantLock有3个静态内部类,分别是Sync、FairSync和NonfairSync。下图是ReentrantLock的简单类图:

这里写图片描述


图3:ReentrantLock简单类图

该图是ReentrantLock的简单类图框架,ReentrantLock的内部类Sync继承自AQS,FairSync和NonfairSync分别继承自Sync,实现了公平锁和非公平锁。

ReentrantLock的lock方法其实是调用Sync的lock方法,而Sync的lock方法是个抽象方法:

abstract void lock();

因此子类需要去实现该抽象方法,非公平锁NonfairSync的实现如下:

    //非公平锁继承了Sync
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            //锁的状态是0代表当前锁是空闲的
            //比较锁的状态是否是0,原子的设置为1,若设置成功,设置独占线程为当前线程
            //此处体现了非公平锁的特点,先尝试获取锁,获取失败再去队列排队获取锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //队列排队获取锁
                acquire(1);
        }
        //稍后会讲解该代码
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

    }

可以看到非公平锁的lock实现思路,首先直接去获取锁,若获取锁成功,将当前锁的占有者设置为当前线程。否则调用acquire方法获取锁。

这里先说明下comareAndSetState方法,类似这种CAS方法在JDK的锁框架中大量使用,JDK的锁正是通过CAS原子操作得以实现。

    



版权声明:本文为luanmousheng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。