ReentrantLock源码

加锁流程

ReentrantLock.lock

java 复制代码
public void lock() {
    sync.lock();
}

sync 是 ReentrantLock 内部抽象类,继承自 AQS

ReentrantLock 构造方法

java 复制代码
// 默认构造:非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

// 传 true = 公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

NonfairSync

lock

java 复制代码
// AQS 变量,state = 0 表示空闲,> 0 表示被占用
private volatile int state;

final void lock() {
	// CAS 操作更改状态
    if (compareAndSetState(0, 1))
    	// 获取锁成功 修改拥有者线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
    	// 获取锁失败
        acquire(1);
}

acquire

线程抢锁成功,直接修改持有者线程,然后退出

如果抢锁失败,进入 acquire 方法

acquire 首先会再次尝试获取锁,依旧获取锁失败后,会创建一个 Node 节点并将 Node 加入等待队列中

java 复制代码
public final void acquire(int arg) {
	// 再次尝试获取锁
    if (!tryAcquire(arg) &&
    	// addWaiter 将当前等待线程封装为 Node 放入队列中
    	// 获取锁失败加入等待队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // acquireQueued 返回 true 会进入到该分支,说明当前抢锁的线程已经被要求中断,所以这里要执行 selfInterrupt
        selfInterrupt();
}

tryAcquire

尝试通过 CAS 获取锁,如果获取成功则返回 true,否则返回 false

java 复制代码
protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}

// 非公平锁尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); // 获取当前 state

    // 分支1:锁空闲 state=0
    if (c == 0) {
        // 再次 CAS 抢锁
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 分支2:锁被占用,判断【是不是当前持有线程】= 可重入逻辑
    else if (current == getExclusiveOwnerThread()) {
        // 重入:state + 1
        int nextc = c + acquires;
        // 重入次数溢出校验(int 最大值保护)
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 分支3:锁被其他线程占用,获取锁失败
    return false;
}

addWaiter

创建节点并加入等待队列

java 复制代码
private Node addWaiter(Node mode) { 
	// mode = Node.EXCLUSIVE
    // 把当前线程 + 独占模式 封装成 Node 节点
    Node node = new Node(Thread.currentThread(), mode);
    // 快速尝试:直接追加到队列尾(无锁入队)
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // CAS 尝试修改尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 快速入队失败(队列空 / 竞争失败),走完整入队逻辑
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        //第一次调用时,tail 为 null
        Node t = tail;
        if (t == null) { // Must initialize
        //尝试设置head=new node
        if (compareAndSetHead(new Node()))
        //将tail设置为head, tail head都为new Node()
        //再进入循环,t=new Node().进入else
        tail = head;
    } else {
        //t=new node ,node为刚才ThreadB的node
        node.prev = t;
        //尝试把ThreadB的node赋值给tail
        if (compareAndSetTail(t, node)) {
            //赋值成功,headnode的后节点指向B的node,tail=B线程节点
            t.next = node;
            //返回head节点,跳出方法
            return t;
        }
    }
}

acquireQueued

  1. 队列中只有头节点的下一个节点才有资格抢锁
  2. 一直自旋,直到将节点的状态修改完成后,再进入阻塞状态
  3. shouldParkAfterFailedAcquire:修改节点等待状态,决定是否调用 park()。
  4. parkAndCheckInterrupt():调用 Unsafe.park () 阻塞当前线程
java 复制代码
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	// 获取 node 的前一个节点
            final Node p = node.predecessor();
            // 如果 p 是头节点 则代表 node 是第一个线程节点, 那就再尝试获取一次锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // shouldParkAfterFailedAcquire 修改节点等待状态,只有 shouldParkAfterFailedAcquire 返回 true 才会执行 parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())	// 等待线程被唤醒后从该方法开始执行
                interrupted = true;
        }
    } finally {
    	// 如果抛出了异常(加锁不成功), failed 会为 true
        if (failed)
        	// 将当前节点的状态设置为 canceled
        	// 对等待节点进行重新排序
        	// 唤醒后继节点
            cancelAcquire(node);
    }
}

//pred为ThreadB的Node的前节点,即head节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // pred.waitStatus默认为0,进入else流程
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
    /*
    * This node has already set status asking a release
    * to signal it, so it can safely park.
    */
    return true;
    if (ws > 0) {
        /*
        * Predecessor was cancelled. Skip over predecessors and
        * indicate retry.
        */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
        * waitStatus must be 0 or PROPAGATE. Indicate that we
        * need a signal, but don't park yet. Caller will need to
        * retry to make sure it cannot acquire before parking.
        */
        //把node的waitStatus从0改成SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

释放锁

ReentrantLock.unlock

java 复制代码
public void unlock() {
    sync.release(1);
}

NonfairSync

release

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;
}

// node 是 队列中的头节点
private void unparkSuccessor(Node node) {
    /*
    * If status is negative (i.e., possibly needing signal) try
    * to clear in anticipation of signalling. It is OK if this
    * fails or if status is changed by waiting thread.
    */
    //ws为-1
    int ws = node.waitStatus;
    if (ws < 0)
    //进入该逻辑,将head节点的waitStatus更改为0
    compareAndSetWaitStatus(node, ws, 0);
    /*
    * Thread to unpark is held in successor, which is normally
    * just the next node. But if cancelled or apparently null,
    * traverse backwards from tail to find the actual
    * non-cancelled successor.
    */
    //找到B线程的node节点
    Node s = node.next;
    //B线程的node节点不为空,waitStatus为-1不进入
    if (s == null || s.waitStatus > 0) {
        s = null;
        //如果B节点失效,或者已经取消或者并发还没有赋值,从后往前找,找到第一个需要执行的 node,t==null或者t==当前head节点,才跳出
        //为什么?enq方法里面,if没有锁 线程A通过CAS进入if语句块之后,发生上下文切换,此时线程B同样执行了该方法,并且执行完毕。然后线程C调用了unparkSuccessor方法。
        // 假如是从头到尾的遍历形式,线程A的next指针此时还是null!也就是说,会出现后续节点被漏掉的情况。
        for (Node t = tail; t != null && t != node; t = t.prev)
        if (t.waitStatus <= 0)
        s = t;
    }
    //得到我需要要unpark的线程,这里为ThreadB
    if (s != null)
    //解锁threadB
    LockSupport.unpark(s.thread);
}

tryRelease

java 复制代码
protected final boolean tryRelease(int releases) {
    // state 减去释放次数
    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;
}

公平锁与非公平锁的区别

公平锁加了 hasQueuedPredecessors(),先判断队列是否有人排队,有人就乖乖排队,不插队

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;
}
相关推荐
嵌入式协会20240721 小时前
(已解决)MinIO python 获取预签名出现forbidden、errornetwork等错误
java·开发语言·python
不才不才不不才2 小时前
Spring AI 实战:聊天、提示词、记忆三件套
java·人工智能·spring·ai
一 乐3 小时前
家政服务管理系统|基于springboot + vue家政服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·家政服务管理系统
碳基硅坊4 小时前
Spring AI:把大模型接进 Spring 应用
java·人工智能·spring ai
黄毛火烧雪下5 小时前
Java 核心知识点总结(一)
java·开发语言
技术小结-李爽5 小时前
【工具】Maven的下载、安装、使用
java·maven
极创信息5 小时前
Linux挖矿病毒深度清理实战教程,从进程隐藏、Rootkit驻留到彻底根除
java·大数据·linux·运维·安全·tomcat·健康医疗
努力成为AK大王5 小时前
并发编程的核心挑战、优化方案与核心知识点总结
java·开发语言·数据库
云烟成雨TD5 小时前
Agent Scope Java 2.x 系列【10】技能(Skill)
java·人工智能·agent