『每日一问』ReentrantLock加锁解锁

本文详细剖析了ReentrantLock的内部实现,包括其与Sync、FairSync、NonfairSync的关系,以及如何通过CAS操作实现线程间的互斥访问。同时,文章还介绍了ReentrantLock的加锁和解锁流程,以及其支持的可重入特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
ReentrantLock 、Sync 、FairSync 、NonfairSync 代码之间的组织关系

public class ReentrantLock{
	abstract static class Sync extends AbstractQueuedSynchronizer{
		//...
	}
	static final class NonfairSync extends Sync {
		//...
	}
	static final class FairSync extends Sync{
		//...
	}
}

Lock接口中定义了六个方法

//加锁
void lock();
//可中断的加锁
void lockInterruptibly() throws InterruptedException;
Condition newCondition();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解锁
void unlock();
加锁
ReentrantLock.lock
	public void lock() {
	    sync.lock();
	}
	//这个sync是ReentrantLock中的一个属性
	private final Sync sync;
	//因为Sync是abstract修饰的,那么这里的Sync是哪一个子类呢?
	//看构造方法,默认就是一个非公平锁
	public ReentrantLock() {
        sync = new NonfairSync();
    }
    //当然,是否公平锁是可以指定的
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
NonfairSync.lock
   @Override
   final void lock() {
       if (compareAndSetState(0, 1)) {
           setExclusiveOwnerThread(Thread.currentThread());
       } else {
           acquire(1);
       }
   }
	
	//首先呢你看到了@Override,那么没错这个lock()就是Sync中的一个抽象方法
	abstract void lock();

再来一步步看实现

1. CAS设置锁状态state的值
	/**
     * 如果当前状态值等于预期值,则自动将同步状态设置为给定的更新值。
	 * 该操作具有{@code volatile}读写的内存语义。
     *
     * @param expect 期待值,也就是你预想的旧值
     * @param update 新的值
     * @return {@code true} if successful. 返回false表示实际值与预期值不相等。
     */
    protected final boolean compareAndSetState(int expect, int update) {
        //unsafe的操作是native操作
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
2. 如果当前线程设置锁状态成功,说明当前线程获取锁成功,那么设置当前线程为持有锁的线程
	/**
	 * 这个方法是AbstractOwnableSynchronizer中的
	 * 		AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的抽象父类,
	 * 里边维护了一个代表当前单独访问的线程引用
	 * 		private transient Thread exclusiveOwnerThread;
	 * 
	 */
	protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
3. 如果当前线程没有成功设置state的状态
	1.尝试再次获取锁,期间要是发现加锁的线程就是当前线程,state++,也就是支持可重入
	2.尝试失败,当前线程创建Node节点,然后以 『CAS + 死循环』 的方式保证放到等待队列中,同时设置当前节点为尾节点 , 
		节点进入同步队列之后,就进入了一个 『自旋』 的过程,每个节点(或者说每个线程)都在自省地观察,
		当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程)
    /**
	 * 这个方法是AbstractOwnableSynchronizer中的
	 * 实现的效果就是
	 * 		如果(尝试获取锁失败 并且 将当前线程加阻塞队列成功){
	 * 			将当前线程中断
	 * 		}
	 */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt(); //Thread.currentThread().interrupt();
    }
    //3.1、tryAcquire(arg);
    /**
	 * 这个方法最终到了nonfairTryAcquire这个方法
	 * 
	 */
	 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
           	//获取当前锁的状态
            int c = getState();
            //如果是无锁状态,也就是state为0
            if (c == 0) {
            	//CAS设置锁状态为1,设置成功后设置当前锁的持有线程
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
			//如果是有锁状态 且 锁的持有线程就是当前线程
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //如果是当前线程,那么state累加,这里就可以发现,Reentrant是可重入的
                setState(nextc);
                return true;
            }
            //如果是有锁状态 且 锁的持有线程不是当前线程,直接返回false,也就是线程尝试获取锁失败
            return false;
     }
     /**
     * 3.2、addWaiter(Node mode)
     * 为当前线程和给定模式创建和排队节点。
     * @param mode Node.EXCLUSIVE for exclusive(独占), Node.SHARED for shared(共享)
     * @return 新的节点
     */
    private Node addWaiter(Node mode) {
    	//创建一个新的node
        Node node = new Node(Thread.currentThread(), mode);
        /**
         * 获取尾部节点
         * AQS中有两个成员变量,用来表示等待队列中的头部以及尾部节点
         * private transient volatile Node head;
         * private transient volatile Node tail;
         */
        Node pred = tail;
        if (pred != null) {
        	//设置新节点的头结点指向原先队列的尾部节点
            node.prev = pred;
            //CAS设置尾节点,保证安全性
            if (compareAndSetTail(pred, node)) {
            	//新节点的next节点指向自己
                pred.next = node;
                return node;
            }
        }
        //前边的CAS可能会失败,这里通过一个死循环来保证设置尾节点一定成功
        enq(node);
        return node;
    }
     private Node enq(final Node node) {
        /**
         * 同步器通过“死循环”来保证节点的正确添加,在“死循环”中只有通过CAS将节点设置成为尾节点之后,当前线程才能从该方法返回,
         * 否则,当前线程不断地尝试设置。可以看出,enq(final Node node)方法将并发添加节点的请求通过CAS变得“串行化”了。
         */
        for (; ; ) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
     }
      /**
     * 3.3、acquireQueued(final Node node, int arg)
     *	
     */
     final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (; ; ) {
                final Node p = node.predecessor(); //就是node.prev
                if (p == head && tryAcquire(arg)) {//只有node.prev是头部节点,才可以去尝试获取锁,也满足了队列的FIFO原则
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
解锁
ReentrantLock.unlock()
	@Override
    public void unlock() {
    	//每次解锁相当于state-=1 , 因为可重入的特性,state会大于1
        sync.release(1);
    }

	//AbstractQueuedSynchronizer.release
	public final boolean release(int arg) {
        if (tryRelease(arg)) {
        	//因为ReentrantLock是支持可重入的,这里只有持有锁的线程释放掉所有的state,
        	//也就是state一直减到0为止,tryRelease才会返回true
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

	//Sync.tryRelease
	/**
	 * state是volatile修饰的一个单一变量,在具有可见性的基础上就具有了原子性
	 */
	@Override
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //如果当前解锁的线程不是锁的持有线程,抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;//标记当前线程是否完全释放锁,也就是state减为0
        if (c == 0) {
        	//state为0,是当前线程完全释放锁标记唯一条件
            free = true;
            //设置当前锁持有线程为null
            setExclusiveOwnerThread(null);
        }
        //回写state
        setState(c);
        return free;
    }
    protected final void setState(int newState) {
        state = newState;
    }

注意:

  • 独占式锁中,线程因为获取同步状态失败之后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出
<think>好的,我现在需要详细解释Java中ReentrantLock加锁机制和使用方法。首先,用户提供的引用资料中提到了ReentrantLock的基本用法,比如lock()和unlock()方法,以及其与AQS(AbstractQueuedSynchronizer)的关系。我需要将这些信息整合起来,形成个清晰的讲解结构。 首先,应该从ReentrantLock的基本概念入手,说明它是个可重入的互斥锁,具有与synchronized相似的功能,但更灵活。然后,引用示例代码展示基本用法,比如在doSomething方法中加锁解锁的过程。这里要注意代码的正确性,比如在finally块中释放锁,确保异常时也能解锁。 接下来,需要深入加锁机制。根据引用[3],ReentrantLock的lock()方法调用了sync.lock(),而sync是继承自AQS的内部类。这里要区分公平锁和非公平锁的不同实现。非公平锁会直接尝试获取锁,如果失败则进入AQS队列;公平锁则会先检查队列是否有等待线程。需要详细解释这两个过程,并结合源码片段,如compareAndSetState(0,1)来展示CAS操作。 然后,可重入性的实现是关键点。要说明锁内部维护了个计数器,记录线程重入的次数,每次lock()计数器加1,unlock()减1,直到为0时释放锁。这部分需要例子说明,比如同个线程多次加锁的情况。 此外,使用注意事项也很重要,比如确保在finally中释放锁,避免死锁。还可以提到ReentrantLock的高级功能,如尝试获取锁、可中断锁等,但根据用户的题,可能需要简要提及,保持重点在基本机制和用法。 最后,生成相关题时要围绕ReentrantLock的特性、AQS的作用、公平与非公平锁的区别、使用注意事项等,确保题能帮助用户进步理解。 需要检查引用资料的正确性,比如引用[4]提到Doug Lea的贡献和AQS,可以用来支持AQS的讲解部分。同时,确保各部分引用标注正确,比如源码部分引用[3]和[4]。 现在组织内容的结构:概述、基本用法、加锁机制(公平与非公平)、可重入性、注意事项、相关题。确保每个部分都有引用支持,并且语言简洁明了,符合用户要求的中文回答,使用正确的LaTeX格式,如代码块用反引号包裹,行内公式用$符号。</think>### ReentrantLock加锁机制及使用详解 #### 1. 基本概念 `ReentrantLock`是Java提供的可重入互斥锁,属于`java.util.concurrent.locks`包。与`synchronized`关键字相比,它支持**公平锁策略**、**可中断锁**和**超时获取锁**等特性[^4]。 #### 2. 基本用法 通过`lock()`加锁,`unlock()`释放锁,需在`finally`块中确保释放: ```java private ReentrantLock lock = new ReentrantLock(); public void doSomething() { lock.lock(); // 加锁 try { // 临界区代码 } finally { lock.unlock(); // 释放锁 } } ``` #### 3. 加锁机制 ##### 3.1 非公平锁(默认) 调用`lock()`时通过CAS操作尝试直接获取锁: ```java final void lock() { if (compareAndSetState(0, 1)) // CAS操作 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 进入AQS队列等待 } ``` 若失败则调用`acquire()`,进入AQS队列排队[^3]。 ##### 3.2 公平锁 构造函数传入`true`启用公平锁: ```java ReentrantLock fairLock = new ReentrantLock(true); ``` 公平锁会先检查AQS队列是否有等待线程,避免插队[^4]。 #### 4. 可重入性实现 通过`state`变量记录重入次数: ```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; setState(nextc); // 增加重入次数 return true; } return false; } ``` #### 5. 注意事项 1. **必须手动释放锁**:`unlock()`需在`finally`中调用,避免死锁 2. **避免锁嵌套**:重入次数需与释放次数严格匹配 3. **性能权衡**:公平锁保证顺序但吞吐量低,非公平锁反之 #### 6. 典型应用场景 - 需要细粒度控制的并发资源访 - 实现带超时的锁获取(`tryLock(long timeout, TimeUnit unit)`) - 处理可中断的阻塞操作(`lockInterruptibly()`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高级摸鱼工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值