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()
相关推荐
贝克街的天才2 分钟前
Java最流行的分布式事务解决方案以及实际操作
java·后端
小兵张健3 分钟前
202502 复盘 工作心态
后端·程序员
尤宸翎10 分钟前
Bash语言的语法
开发语言·后端·golang
橙子家10 分钟前
nginx 简单实践:负载均衡【nginx 实践系列之四】
后端
Ho1aAs14 分钟前
『Rust』Rust运行环境搭建
开发语言·后端·rust
奔跑吧邓邓子17 分钟前
【商城实战(37)】Spring Boot配置优化:解锁高效商城开发密码
java·spring boot·后端·配置优化·商城实战
Cloud_.24 分钟前
RabbitMQ 基本原理详解
spring boot·分布式·后端·rabbitmq
阿黄学技术28 分钟前
Spring单例Bean的线程安全
java·后端·spring
追逐时光者29 分钟前
工作面试必备:SQL 中的各种连接 JOIN 的区别总结
后端·sql
乘风!30 分钟前
SpringBoot前后端不分离,前端如何解析后端返回html所携带的参数
前端·spring boot·后端