JDK ReentrantLock可重入锁和AbstractQueuedSynchronizer队列同步器源码解析

队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组 件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。

同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法(这些需要被重写的方法并没有被定义成抽象方法,而是在方法中抛出了UnsupportedOperationException这种异常),随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

下面是ReentrantLock可重入锁的基本代码示例。

public class Start {
    public static void main(String args[]) {
        ReentrantLock lock=new ReentrantLock();
        lock.lock();
        try {
            System.out.println("Your synchronized code here.");
        }finally {
            lock.unlock();
        }
    }
}

上面这段代码中finally 保证了锁的释放。接下来我们看一下调用lock方法后会做什么。

ReentrantLock实现了Lock接口,如下是ReentrantLock的实现代码。

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

上面这段方法代码调用了ReentrantLock的内部抽象类Sync的lokc抽象方法(说法可能不太好,抽象方法是不能直接调用的,调用的肯定是具体的实现类的方法),Sync继承了AbstractQueuedSynchronizer。抽象类Sync中除了lock抽象方法之外只有一个nonfairTryAcquire方法了。在ReentrantLock中还有两个内部类NonfairSync和FairSync,它们实现了Sync的lokc抽象方法,很明显Nonfair和Fair代表了公平和不公平,这就是ReentrantLock支持公平和非公平锁的间接实现(公平锁可以简单描述为先请求锁的人先能先拿到锁,就像你去食堂打饭一样。非公平锁可以简单描述为全凭运气看谁能先拿到锁,就像你抽奖一样,先抽奖和后抽奖的人中奖的几率不存在先来的人中奖几率大,但是ReentrantLock貌似不完全符合这样的规律)。

ReentrantLock一共有两个构造函数,一个无参构造函数和一个带一个布尔类型参数的构造函数。

下面是ReentrantLock的无参数构造函数的源码。

public ReentrantLock() {
    sync = new NonfairSync();
}

下面是ReentrantLock的一个带一个布尔类型参数的构造函数的源码。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

从上面两段源码可以看出ReentrantLock默认是非公平锁。

这里以非公平锁为例,查看NonfairSync类实现源码如下。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

先来看lokc方法,compareAndSetState方法在AbstractQueuedSynchronizer中已经实现,其最终会调用一个由native关健字修饰的方法,不再描述,这里只解释compareAndSetState方法的作用,compareAndSetState方法是满足原子更新的,包含一个你期望的当前值和你期望设置的新的值,即比较再交换,是线程安全的。在AbstractQueuedSynchronizer有如下成员变量的定义。

private volatile int state;

其中的volatile关键字保证了共享变量的线程安全,volatile关键字会锁住总线或者内存。

再回到NonfairSync的代码当中其中的if语句的意思就很好理解了(在锁的层面,不是同步器的层面,后续也都在锁的层面上来叙述),即如果当前没有线程持有锁,就把锁给到当前请求锁的线程。如果已经被其它线程持有了锁,就调用acquire方法。

acquire方法在AbstractQueuedSynchronizer已经实现,查看acquire的源码如下。

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

代码当中的tryAcquire方法就是刚才我们在NonfairSync中的实现,如下。

 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) {
        if (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;
}

nonfairTryAcquire方法中依次判断了锁是否被其他线程持有或者当前线程就是锁的持有者,其中当前线程是否是锁的持有者就和锁的可重入有关。

下面是公平锁FairSync类的tryAcquire方法的代码。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        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;
}

对比非公平锁nonfairTryAcquirenonfairTryAcquire方法的代码和公平锁FairSync类的tryAcquire方法的代码发现在第一个if的时候公平锁FairSync类的tryAcquire方法的代码多使用了一个hasQueuedPredecessors方法,这个方法是公平锁的关键,这样使得当前持有锁的线程释放锁之后下一个拿到锁的线程总是队列中head的下一个,而非公平锁取决于谁的compareAndSetState方法先执行成功。这里再强调一下volatile关键字,它保证了共享变量的线程安全,volatile关键字会锁住总线或者内存,即JVM内存的缓存一致性协议。

接下来回到acquire方法,首先查看addWaiter的源代码。

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mod
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

addWaiter方法的作用就是将当前线程放入等待锁的队列尾节点,在addWaiter方法的外层是acquireQueued方法,代码如下。

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;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这个方法在for循环中(死循环)等待锁获取成功,其中的shouldParkAfterFailedAcquire和parkAndCheckInterrupt用于阻塞当前线程,将当前线程置于WAITING状态等待唤醒。

接下来查看ReentrantLock的unlock解锁方法。

public void unlock() {
    sync.release(1);
}

该方法调用了抽象类Sync的父类AbstractQueuedSynchronizer的release方法,因为无论公平锁还是非公平锁它们的解锁逻辑都一致,release方法的源码如下。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

其中tryRelease方法由抽象类Sync实现,源码如下。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

可以看到每调用一次tryRelease方法就会将state的值减去1,这便是重入锁的解锁逻辑,这里再次强调volatile关键字。当state的值等于0,即锁释放完毕后就会将持有当前锁的线程置为空。

到这里ReentrantLock的加锁和解锁逻辑基本描述得差不多了,其它逻辑可以依葫芦画瓢。可以看一下超时返回tryLock方法的实现,如下。

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

查看tryAcquireNanos方法如下。

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

这里不再叙述tryAcquire方法,重点关注doAcquireNanos方法,源码如下。

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这里和tryAcquire方法的逻辑很像,主要的不同是多了超时判断,判断代码如下。

nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
    return false;

在Java多线程编程中最常见的就是volatile关键字、synchronized关键字和Lock接口的运用,其中volatile关键字主要保证了共享变量的多处理器多线程间的内存可见性,synchronized主要是使用CAS和一些列策略来实现代码块的加锁,Lock接口结合volatile关键字和CAS算法等来实现代码块的加锁,synchronized的加锁和解锁是隐式的,Lock接口的加锁和解锁是显式的,但是Lock接口的加锁和解锁更加灵活。

在这里对ReentrantLock的源码做了一些基本的分析,后续可以依葫芦画瓢对各种基于Redis的分布式锁的实现做分析。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值