源码解读 | Java中ReentrantLock的实现原理

本文将介绍Java中ReentrantLock的实现原理,从源码层面讲解公平锁和非公平锁的加锁、释放锁的流程,以及条件变量的实现。

ReentrantLock 依赖关系如下图所示:

在这里插入图片描述

非公平锁实现原理

ReentrantLock 默认采用非公平锁。

// ReentrantLock
public ReentrantLock() {
   
    sync = new NonfairSync();
}
加锁流程

ReentrantLock 的 lock 方法通过同步器的 lock 方法实现。

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

同步器的 lock 方法会先调用 initialTryLock() 方法,如果失败,则调用 acquire() 方法。

// Sync extends AbstractQueuedSynchronizer
final void lock() {
   
    if (!initialTryLock())
        acquire(1);
}
// AbstractQueuedSynchronizer
public final void acquire(int arg) {
   
    // 尝试获取锁
    if (!tryAcquire(arg))
        // 获取锁失败则加入的等待队列
        // null, 自定义参数, 非共享锁, 不可打断, 无时限, 时限 
        acquire(null, arg, false, false, false, 0L);
}

NonfairSync 的 initialTryLock 分两步:尝试获取锁、尝试重入。

NonfairSync 的 tryAcquire 方法被调用前,必定会调用 initialTryLock() 方法检查锁是否被当前线程持有,也即,调用 tryAcquire 方法时,锁必定未被当前线程持有。因此,当未有线程持有锁时,tryAcquire 才能尝试获取锁。

// NonfairSync extends Sync
static final class NonfairSync extends Sync {
   
    
    // 初次尝试获取锁
    final boolean initialTryLock() {
   
        Thread current = Thread.currentThread();
        // 通过CAS尝试获取锁
        if (compareAndSetState(0, 1)) {
   
            // 将锁的持有者设为当前线程
            setExclusiveOwnerThread(current);
            return true;
        } 
        // 尝试获取锁失败,判断锁的持有者是否为当前线程
        else if (getExclusiveOwnerThread() == current) {
   
            // 锁重入
            int c = getState() + 1;
            // 整数溢出
            if (c < 0) 
                throw new Error("Maximum lock count exceeded");
            // 设置锁的状态:state 表示重入次数
            setState(c);
            return true;
        } else
            return false;
    }

    // 非初次尝试获取锁
    protected final boolean tryAcquire(int acquires) {
   
        // 未有线程持有锁时,当前线程尝试获取锁
        if (getState() == 0 && compareAndSetState(0, acquires)) {
   
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

AbstractQueuedSynchronizer 的 acquire 方法负责将线程加入等待队列,acquire 方法的主要执行步骤如下:

  1. 若非第一个等待线程且前驱存在,则检查当前节点前驱是否已被取消(前驱线程被取消)
    • 若已被取消,则需要从队尾开始,往前清理已取消的节点,进入下一轮循环。
    • 若未被取消,检查当前线程是否已成为第一个线程,若是,则自旋等待,进入下一轮循环
  2. 若是第一个等待线程或者前驱不存在,则尝试获取锁,获取锁成功则直接返回 1 表示获取锁成功。
  3. 准备将当前线程加入等待队列
    • 3.1 若等待队列未创建,则创建等待队列,进入下一轮循环
    • 3.2 否则,若节点未创建,则创建节点,进入下一轮循环
    • 3.3 否则,若节点信息未设置,则设置节点信息(包括将节点加入队尾),进入下一轮循环
    • 3.4 否则,若为第一个等待线程且自旋次数不为 0,提示 JVM 当前线程正在忙等,进入下一轮循环
    • 3.5 否则,若节点状态为 0(默认),则将节点状态设为 1(WAITING)
    • 3.6 否则,重置自旋自旋次数,当前线程陷入等待,被唤醒后将等待状态置为 0。
// AbstractQueuedSynchronizer
final int acquire(
    // 尝试获取锁的节点
    Node node, 
    // 自定义参数
    int arg, 
    // 是否为共享锁
    boolean shared,
    // 是否可打断
    boolean interruptible, 
    // 是否带时限
    boolean timed,
    // 时限
    long time
) {
   
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;  
    boolean interrupted = false, first = false;
    Node pred = null;               
    
    for (;;) {
   
        // 检查前驱是否被取消
        if (
            // 非第一个等待线程
            !first && 
            // 前驱存在
            // 除非本方法被ConditionNode.await()方法调用,否则node初始必为null
            // 也即,此条件在第一轮循环必为false
            (pred = (node == null) ? null : node.prev) != null &&
            // 非第一个等待线程
            !(first = (head == pred))
        ) {
   
            if (pred.status < 0) {
   
                // 若前驱被取消,则需要清理队列中取消的线程
                // 此举是为确保给当前节点的前驱能唤醒当前节点
                cleanQueue(); 
                continue;
            } else if (pred.prev == null) {
   
                // 若前驱的前驱为null,说明当前线程是第一个线程
                // !first 和 pred.prev == null 之间,head的值被修改,导致当前线程成为第一个线程
                // 第一个线程自旋等待,以减少线程切换的开销
                Thread.onSpinWait();
                continue;
            }
        }
        // 尝试获取锁
        if (
            // 第一个线程 或者 前驱不存在
            first || pred == null
        ) {
   
            boolean acquired;
            // 尝试获取锁
            try {
   
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
   
                cancelAcquire
  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java的互斥锁是通过`ReentrantLock`来实现的,它的实现原理是基于AQS(AbstractQueuedSynchronizer)同步器的。AQS是一个用于实现同步器的框架,它提供了两种同步状态,分别是独占模式和共享模式。 当一个线程请求锁时,如果锁没有被其他线程占用,则该线程成功获取锁并进入独占模式,此时其他线程再去请求锁时就会被阻塞。如果此时有其他线程已经占用了锁,则当前线程会被加入到一个等待队列,并且进入阻塞状态。 在`ReentrantLock`,还提供了可重入特性,也就是说同一个线程可以多次获取同一个锁,而不会被阻塞。这个特性是通过一个计数器来实现的,每当一个线程获取锁时,计数器加1,释放锁时计数器减1,这样同一个线程可以多次获取锁而不会阻塞。 `ReentrantLock`的实现原理主要包括以下几个步骤: 1. 初始化:初始化一个AQS同步器,同时初始化一个等待队列和一个线程对象; 2. 获取锁:如果锁未被占用,则当前线程获取锁并进入独占模式;如果锁已经被占用,则当前线程加入到等待队列,并且进入阻塞状态; 3. 释放锁:当前线程释放锁,并且计数器减1,同时唤醒等待队列的一个线程; 4. 可断获取锁:如果当前线程在等待锁的过程断,则会抛出`InterruptedException`异常。 总之,`ReentrantLock`是Java一种功能强大的互斥锁实现方式,它能够支持可重入特性、公平和非公平锁、可断获取锁等多种功能。在多线程编程,使用`ReentrantLock`可以有效地避免线程竞争和死锁等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值