JUC之Reentrantlock源码解析

前言

Reentrantlock是可重入互斥锁,具有与Synchronized相同的功能,但是却比Synchronized更加灵活

Reentrantlock底层基于AbstractQueuedSynchronizer实现,AbstractQueuedSynchronizer抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,只有少量细节需要自己设定。(AbstractQueuedSynchronizer为加锁和解锁过程提供了同步的模板方法)。

Reentrantlock结构构成

在这里插入图片描述

Reentrantlock实现了Lock接口,Reentrantlock具有内部类SyncNonfairSyncFairSync,其中Sync继承于AbstractQueuedSynchronizerSync实现了释放资源的细节,NonfairSync是Sync的子类,实现锁的非公平模式,FairSync也是Sync的子类,实现了锁的公平模式。

源码剖析

了解了Reentrantlock的整体结构,接下来我们一个块一个块地去分析。

首先我们学习一下Lock接口的源码

public interface Lock {

    // 获取 - 不可中断
    void lock();
    // 获取 - 可以中断
    void lockInterruptibly() throws InterruptedException;
    // 获取锁 - 可以返回获取锁是否成功
    boolean tryLock();
    // 获取锁(可以设置时间) - 可以中断
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁
    void unlock();
    // 条件变量
    Condition newCondition();
}
/*
这里的中断指的是如果当前线程获取锁但是未获取到锁,我们可以使用中断方法中断获取锁,而不会使它陷入阻塞状态。
*/

AQS部分解析

AQS中有一个CLH队列,当获取锁失败时会将线程加入CLH队列,当一个锁是非公平锁时,当前线程获取锁时会和队列的首部线程争抢,成功则设置当前线程为获取锁线程,失败则加入队列;当一个锁是公平锁时,如果队列为空,则当前线程获取锁,如果队列不为空,则一定是队列首部线程获取到锁,将当前线程加入CLH队列

在这里插入图片描述

Sync源码

在这里插入图片描述

Reentrantlock中有一个Sync对象,当我们使用的锁为非公平锁时,可以利用多态的特性,使其指向NonfairSync,同理,当我们使用的锁为公平锁的时候,我们可以将其指向FairSync。又因为Sync继承了AbstractQueuedSynchronizerAbstractQueuedSynchronizer提供了加锁和解锁过程的模板方法,因此我们可以看出SyncReentrantlock中的重要性。

接下来我们就来看看Sync的源码

//Sync的部分源码
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
    
    // 获取锁,由子类实现
    abstract void lock();

    // 非公平获取资源
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前状态
        int c = getState();
        // 如果state == 0, 说明现在没有线程获取到锁,则当前线程尝试加锁
        if (c == 0) {
            // 尝试加锁
            if (compareAndSetState(0, acquires)) {
                // 加锁成功,设置当前线程为获取锁线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果state != 0, 但是当前线程已经是获取到锁的线程,说明当前可以多次获取锁(可重入)
        else if (current == getExclusiveOwnerThread()) {
            // 获取资源,state + 1
            int nextc = c + acquires;
            // 如果state < 0, 抛出异常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 设置state的状态,此处不需要CAS,因为当前持有锁的线程只有一个,就是自己。
            setState(nextc);
            // 返回true
            return true;
        }
        // 返回false
        return false;
    }
	// 释放资源
    protected final boolean tryRelease(int releases) {
        // state 减去 releases
        int c = getState() - releases;
        // 如果当前线程不是获取锁的线程,那肯定无法释放资源,就抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        // 默认释放资源为false
        boolean free = false;
        // 如果state为0,说明已经释放了全部资源,设置当前持有锁的线程为null,返回true
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        // 否则说明没有释放全部资源(重入锁),返回false
        setState(c);
        return free;
    }
    // 创建条件变量
	final ConditionObject newCondition() {
            return new ConditionObject();
        }
}

我们可以发现,Sync实现了非公平获取锁的方法,这是给子类NonfairSync用的,但是却没有实现公平获取锁的方法,需要子类FairSync自己实现。另外Sync还实现了释放资源的方法。

释放资源的流程:

  1. 判断当前线程是否是持有锁的线程,是就往下执行,不是则抛出异常
  2. 得到state减1的值,之后判断state是否等于0,等于0设置持有锁线程为null,返回true表示释放资源成功,不等于0就设置state为state-1,返回false,代表释放资源失败(重入锁)。

这是释放资源的流程图

在这里插入图片描述

说完了Sync,那么我们接下来说一下NonfairSync

NonfairSync

NonfairSyncSync的子类,用于非公平获取资源

// NonfairSync的源码
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

	// 加锁
    final void lock() {
        // 执行CAS操作
        if (compareAndSetState(0, 1))
            // CAS操作成功,设置当前线程为持有锁线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 失败执行AQS获取锁模板流程
            acquire(1);
    }
	// 获取资源,调用父类Sync实现的非公平获取资源方法
    protected final boolean tryAcquire(int acquires) {
        // 直接使用父类的非公平获取资源
        return nonfairTryAcquire(acquires);
    }
}
// AQS中的acquire(int arg)方法
public final void acquire(int arg) {
    	// 再执行一次获取资源方法,成功就结束,失败就将当前线程加入CLH队列
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
/*
我们看一下父类Sync实现非公平获取资源的细节
	// 非公平获取资源
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前状态
        int c = getState();
        // 如果state == 0, 说明现在没有线程获取到锁,则当前线程尝试加锁
        if (c == 0) {
            // 尝试加锁
            if (compareAndSetState(0, acquires)) {
                // 加锁成功,设置当前线程为获取锁线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果state != 0, 但是当前线程已经是获取到锁的线程,说明当前可以多次获取锁(可重入)
        else if (current == getExclusiveOwnerThread()) {
            // 获取资源,state + 1
            int nextc = c + acquires;
            // 如果state < 0, 抛出异常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 设置state的状态,此处不需要CAS,因为当前持有锁的线程只有一个,就是自己。
            setState(nextc);
            // 返回true
            return true;
        }
        // 返回false
        return false;
    }
*/

我们说一下非公平获取资源的方式:

  1. 判断state是否等于0,等于0就尝试加锁,加锁成功就设置当前线程为持有锁的线程,返回true,否则往下执行
  2. 判断当前线程获取锁的线程是不是同一个线程,是同一个线程,就让state+1,返回true,否则返回false

在这里插入图片描述

FairSync 解析

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
	// 执行AQS获取锁模板函数
    final void lock() {
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // state为0,之后判断当前线程是不是队列被唤醒的线程
        if (c == 0) {
            // 是队列被唤醒的线程,尝试加锁
            // hasQueuedPredecessors()返回false说明当前线程是队列的头部线程,就尝试获取锁
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 加锁成功就设置当前线程为持有锁的线程,返回true
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 当前线程和持有锁的线程是同一个线程,就让state+1,设置state,返回true,否则返回false
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 设置state
            setState(nextc);
            return true;
        }
        return false;
    }
}

FairSync公平获取锁,获取资源流程和NonfairSync获取资源的流程很相似,只是加了一个判断,判断如果当前线程不在队列就加入队列,如果当前线程在队列就判断当前线程的前面还有没有线程。

在这里插入图片描述

Reentrantlock

最后我们说一下Reentrantlock的源码

// Reentrantlock的部分源码
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    // Sync对象
    private final Sync sync;
    // 默认为非公平锁
    public ReentrantLock() {
            sync = new NonfairSync();
        }
    // 设置锁为公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // 获取锁
    public void lock() {
            sync.lock();
        }
    // 获取锁 - 可以中断
    public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }
    // 获取锁 - 返回获取锁成功还是失败
    public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    // 获取锁 - 可以设置时间,可以中断
    public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
    // 释放锁
    public void unlock() {
            // 使用Sync的release方法
            sync.release(1);
        }
    // 条件变量
    public Condition newCondition() {
            // 使用Sync的创建条件变量方法
            return sync.newCondition();
        }    

}

Reentrantlock有两个构造方法,无参的构造方法为非公平锁有参的构造方法可以设置公平锁Reentrantlock锁的实现重点靠里面的Sync对象,Sync继承AQS,又有两个子类,使用Sync对象可以很好的实现功能。

结尾

最后我们说一下与Synchronized相比Reentrantlock有以下几个特性

  • 重入
  • 中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 锁的粒度更小,功能更灵活

相信学习了Reentrantlock的结构和内部源码,我们对于它的使用肯定可以更加得心应手。

学习于:
程序猿阿星
米兰的小铁匠z

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值