【并发编程】ReentrantLock

ReentrantLock是什么

    ReentrantLock继承自AbstractQueuedSynchronizer,即AQS, AQS可以理解为一个线程排队获取CPU的队列。队列中放的是一个个的Node。

  *      +------+  prev +-----+       +-----+
  * head |      | <---- |     | <---- |     |  tail
  *      +------+       +-----+       +-----+
 	volatile Node prev;
 	volatile Node next;
 	volatile Thread thread;

加锁过程

通过以下代码模拟多线程竞争锁的场景

public class Example1 {
	public static void main(String[] args) {
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
		ReentrantLock reentrantLock = new ReentrantLock(true);
		for (int i = 0; i < 3; i++) {
			threadPoolExecutor.submit(() -> {
				reentrantLock.lock();
				logic();
				reentrantLock.unlock();
			});
		}
		threadPoolExecutor.shutdown();
	}

	public static void logic() {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

head == tail == null

在这里插入图片描述

  • 第一个线程调用tryAcquire(arg)过来获取锁,此时state为0,说明锁是空闲状态,可以继续执行获取流程。
  • 接下来尝试获取锁,首先通过hasQueuedPredecessors()判断队列有没有线程在排队,第一个线程过来时队列时空的,head==tail ==null,即没有线程排队。
  • 直接尝试CAS获取锁
    在这里插入图片描述
  • 如果获取成功,记录锁被当前线程持有,setExclusiveOwnerThread(current)。

head == tail != null

  • 第二个线程调用tryAcquire(arg)过来获取锁,此时state为1,不会继续执行获取锁的流程,但是会判断是否重入锁。
  • 如果getExclusiveOwnerThread()是当前线程,则该线程可以得到锁。
  • 否则进入队列
    在这里插入图片描述
    入队列之前会将当前线程封装成一个Node,Node的前一个节点prev为第一个线程对应的Node,Node的下一个节点next为null,即该Node为tail节点。
    在这里插入图片描述
  • 入队列之后继续尝试获取锁
    在这里插入图片描述
	// 当该节点的上一个节点是head节点时才尝试再次获取锁
	if (p == head && tryAcquire(arg)) {
		setHead(node);
	    p.next = null; // help GC
	    failed = false;
	    return interrupted;
	}
  • 第二个线程走到这里是满足条件的,可以获取到锁。
    在这里插入图片描述

head != tail

  • 第三个线程调用tryAcquire(arg)过来获取锁,此时state为1,不会继续执行获取锁的流程。
  • 进入队列继续尝试获取锁。
  • 第三个线程的前一个Node不是head,所以不能紧接着尝试获取锁。
    在这里插入图片描述
  • 判断是否需要park,第三个线程此时不需要park。(暂未模拟到需要park的场景)
  • 尝试获取锁。
  • 获取锁成功。

解锁过程

  • tryRelease(1)尝试解锁
    在这里插入图片描述
    在这里插入图片描述
    tryRelease依据state是否为0来判断是否解锁成功,不为0时说明存在锁重入的情况。
  • 解锁成功判断队列中是否还有Node排队
    在这里插入图片描述
    如果还有Node在队列中,则调用LockSupport.unpark唤醒排队线程。
### Java 并发编程 ReentrantLock 使用方法 #### 锁的获取与释放 `ReentrantLock` 提供了多种方式来获取锁,其中最基本的方式是通过 `lock()` 方法。调用此方法会尝试获取锁;如果锁已被其他线程持有,则当前线程会被阻塞,直到能够成功获得锁[^1]。 ```java ReentrantLock lock = new ReentrantLock(); lock.lock(); // 获取锁 try { // 执行临界区代码 } finally { lock.unlock(); // 释放锁 } ``` 为了防止死锁或其他异常情况的发生,在使用 `lock()` 后应当总是确保在 `finally` 块中调用 `unlock()` 来释放锁,即使发生错误也应如此处理以避免资源泄露[^4]。 #### 支持可中断的锁请求 除了基本的锁定操作外,还提供了带有中断特性的版本—`lockInterruptibly()` 。这允许正在等待锁的过程中被外部因素打断,并抛出 `InterruptedException` 异常给调用者知道发生了什么状况[^2]。 ```java ReentrantLock interruptibleLock = new ReentrantLock(); try { interruptibleLock.lockInterruptibly(); // 可能因外界干扰而失败并抛出 InterruptedException try { // 处理受保护的操作... } finally { interruptibleLock.unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复被打断的状态标志位 throw new RuntimeException(e); } ``` #### 非阻塞式的尝试加锁 对于那些不想长时间挂起的情况来说,可以考虑采用 `tryLock()` 或带超时参数的方法来进行非阻塞性质上的尝试性上锁动作。前者会在无法立即取得所有权的情况下立刻返回false值而不做任何停留;后者则是在规定时间内轮询检查是否有可用的机会去占有互斥对象[^3]。 ```java // 不等待直接测试能否拿到锁 if (!myLock.tryLock()) { System.out.println("未能即时得到锁"); } // 设置最大等待时间为5秒 if (myLock.tryLock(5, TimeUnit.SECONDS)) { try { // 成功获得了锁后的逻辑 } finally { myLock.unlock(); } } else { System.out.println("超过设定时间未获授权"); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值