ReentrantLock源码分析

核心结构

通过Sync静态内部类继承AQS实现锁的机制,两个子类FairSync 和 NonfairSync 分别对应公平和非公平锁。 有关AQS的原理参考juejin.cn/post/748158... ,本文涉及AQS的方法不再重复分析。

加锁

NonfairSync 非公平锁加锁

scss 复制代码
final void lock() {
    //CAS设置为1,成功加锁成功
    if (compareAndSetState(0, 1))
        //设置独占线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //否则尝试获取锁
        //AQS中的方法
        acquire(1);
}

acquire(int arg)

scss 复制代码
public final void acquire(int arg) {
    if (
    //尝试获获取锁
    !tryAcquire(arg) &&
        //失败加入同步队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //需要中断则中断
        selfInterrupt();
}
arduino 复制代码
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
java 复制代码
final boolean nonfairTryAcquire(int acquires) {
    //当前线程
    final Thread current = Thread.currentThread();
    //获取状态
    int c = getState();
    //状态为0说明之前获取锁的线程已经释放,可以去加锁操作了
    if (c == 0) {
        //将state设置为1,成功则加锁成功
        if (compareAndSetState(0, acquires)) {
            //设置当前线程独占标识
            setExclusiveOwnerThread(current);
            //返回加锁成功
            return true;
        }
    }
    //是独占线程又来获取锁了
    else if (current == getExclusiveOwnerThread()) {
        //状态为之前的状态值加1
        int nextc = c + acquires;
        //无效状态抛异常
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //设置状态值    
        setState(nextc);
        //返回加锁成功
        return true;
    }
    //加锁失败
    return false;
}

非公平锁加锁逻辑总结

  1. 先尝试获取锁,如果加锁成功设置独占标识然后返回,失败则调用AQS中的方法尝试获取锁;
  2. 尝试获取锁,判断状态为0说明没有线程在执行同步代码块,可以去加锁,成功设置独占标识;判断是否是获取锁的线程再次去获取锁,是的话在之前的状态值加1(锁的重入)
  3. 获取锁失败加入同步队列,当前加锁节点的前驱节点是头节点再次尝试获取锁,否则自旋排队直到前驱节点是头节点。

FairSync 公平锁加锁

lock()

scss 复制代码
final void lock() {
    acquire(1);
}
scss 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
java 复制代码
    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;
    }
}
java 复制代码
public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return
    //h==t的话说明队列是空的或者队列仅有一个无效等待的节点
    //h!=t说明同步队列中存在有效节点在排队
    h != t &&
        //说明头节点已有值(end()方法入队时候pre已经设置了值,但tail还没来得及设置,这时候认为有节点在排队)
        ((s = h.next) == null || 
        //不等于当前线程,说明有其他节点在排队
        s.thread != Thread.currentThread());
}

公平锁加锁逻辑总结: 区别在于hasQueuedPredecessors()方法

解锁

release(int arg)

java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
scss 复制代码
protected final boolean tryRelease(int releases) {
    //状态减1
    int c = getState() - releases;
    //必须要是加锁的线程去解锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //状态是0说明释放锁成功了
    if (c == 0) {
        free = true;
        //清楚独占线程标识
        setExclusiveOwnerThread(null);
    }
    //设置state值
    setState(c);
    return free;
}

解锁逻辑总结: 公平锁和非公平锁解锁是相同的逻辑,把state值减为0,解锁成功。

Condition 操作

arduino 复制代码
final ConditionObject newCondition() {
    return new ConditionObject();
}
csharp 复制代码
//AQS中的
public ConditionObject() { }

对于ReentrantLock的condition 等待和唤醒机制是AQS中的代码,本文不再重复分析,简单总结下逻辑。

  • 等待await()。构建一个条件节点加入到条件队列,释放锁,阻塞park()线程直到唤醒signal()重新获取锁。
  • 唤醒signal()。从条件队列对头节点迁移到同步队列队尾并取消阻塞unpark()
相关推荐
CHENWENFEIc35 分钟前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试
重庆小透明1 小时前
力扣刷题记录【1】146.LRU缓存
java·后端·学习·算法·leetcode·缓存
博观而约取2 小时前
Django 数据迁移全解析:makemigrations & migrate 常见错误与解决方案
后端·python·django
寻月隐君2 小时前
Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式
后端·rust·github
GO兔2 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go
Sincerelyplz3 小时前
【Temproal】快速了解Temproal的核心概念以及使用
笔记·后端·开源
爱上语文3 小时前
Redis基础(6):SpringDataRedis
数据库·redis·后端
Lemon程序馆3 小时前
速通 GO 垃圾回收机制
后端·go
Aurora_NeAr3 小时前
Spark SQL架构及高级用法
大数据·后端·spark
杰尼橙子3 小时前
DPDK BPF:将eBPF虚拟机的灵活性带入到了DPDK的高性能用户态
后端·性能优化