ReentrantLock原理从开始到放弃

本文详细介绍了Java中的ReentrantLock,包括其使用、特性、原理及AQS(AbstractQueuedSynchronizer)的工作方式。ReentrantLock支持可重入、响应中断、尝试加锁和限时等待等功能,其内部依赖AQS实现锁的管理。文章讨论了公平锁与非公平锁的区别,并分析了lock、tryLock、lockInterruptibly和tryLock(long timeout, TimeUnit unit)等方法的实现细节,指出AQS借助LockSupport的park()和unPark()方法进行线程阻塞和唤醒。" 111992201,7299009,SpringBoot与ShardingSphere5.x实现分库分表雪花ID解决方案,"['ShardingSphere5', 'spring boot', '分布式', '大数据']
摘要由CSDN通过智能技术生成

之前写了篇文章介绍了synchronized的原理《Java synchronized 原理从开始到放弃》,而本篇是介绍另外一个高频出现在并发场景的类ReentrantLock,有必要深入理解它的使用和原理。

如果你不想看源码的话,直接看图示 + 总结就能摸清整体的流程了。

使用

ReentrantLock使用起来很简单,例如:

ReentrantLock lock = new ReenTrantLock;
...
try {
   
	lock.lock(); // 加锁
	//do something();
} finally {
   
	lock.unlock(); // 解锁
}

ReentrantLock 使用起来很简单,甚至比synchronized更直观,直接对当前线程上锁、解锁,不止如此,ReentrantLock还有更多的功能:

  1. lock() 获得锁就返回true,不能的话一直等待获得锁
  2. tryLock() 尝试获取锁成功就返回true,不能就直接返回false,不会等待
  3. tryLock(long timeout,TimeUnit unit) 跟tryLock一样,尝试获取锁,如果超过该时间段还没获得锁,返回false
  4. lockInterruptibly 获取锁,跟lock不一样的地方是,过程中会检测是否中断(interrupt),若是会抛出异常
  5. 构造函数 public ReentrantLock(boolean fair) 可设置公平锁还是非公平锁

这里不再细说ReentrantLock的使用方法。

特性

  1. 可重入锁
  2. 可响应中断
  3. 可尝试加锁
  4. 限时等待尝试加锁
  5. 公平锁、非公平锁
  6. 与Condition信号量结合使用

原理

看下ReentrantLock的源码,其中有个重要的变量sync,是一个继承AbstractQueuedSynchronizer (以下称为AQS) 的Sync抽象类,分别由FairSync、NonfairSync类实现,代表着公平和非公平策略。

继承关系

可见,ReentrantLock的实现基本依靠AQS实现的,所以如果要理解ReentranLock,有必要学习AQS。

AQS(AbstractQueuedSynchronizer)

AQS 基本数据结构是一个FIFO的双向队列,每个结点Node存储线程和其他信息。队列的头部结点表示:该结点对应的线程已经处于执行状态,占用了资源;剩下的队列里的线程则被挂起等待唤醒。

在这里插入图片描述

接着上面的示意图,来看一下Node结点类:

在这里插入图片描述

AQS 有两种模式,一种是独占模式 EXCLUSIVE ,另外一种是共享模式 SHARED;而ReentrantLock是独占模式。

SHARED 共享模式,表示可以多个线程同时执行
EXCLUSIVE 独占模式,表示只有一个线程能执行
CANCELLED:1 取消状态,表示这个结点被取消了,可能是被主动取消或者超时,后续这个结点会被踢出队列

SIGNAL: -1 通知后继结点,表示这个结点执行完成以后,需要通知唤醒后继的结点
CONDITION:-2 说明这个结点因为被某一个condition挂起了
PROPAGATE:-3 在共享模式下,下一次获取锁后可以无限传播(不太懂这个意思,后续再好好理解)

除了以上常量,再看下几个变量

waitStatus 当前结点的状态,默认是0,可以是CANCELL、SIGNAL 等
prev 前继结点
next 后继结点
thread 对应的线程
nextWaiter 下一个等待condition的结点
state 状态;是一个int值,表示当前线程占用资源的数量;0表示空闲,没有线程占用;ReentrantLock的state表示线程重入锁的次数

在AQS里面的方法分成两类:

  • acquire()、acquireInterruptibly()、release() 不以share结尾的方法名;
  • acquireShared()、acquireSharedInterruptibly()、releaseShared() 以share结尾的方法名;
    顾名思义, 以share结尾的方法用于共享模式,不以share结尾的方法用于独占模式。

有了以上的了解,可以开始分析源码了

ReentrantLock 的抽象类Sync有两个实现: FairSync (公平策略) 、NonfairSync(非公平策略)

1.lock()

ReentrantLock 的 lock 在FairSync和NonfairSync的方式有一些不同

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

以下分成两种NonfairSync.lock 和 FairSync.lock:

(1) NonfairSync.lock

// NonfairSync
final void lock() {
   
     if (compareAndSetState(0, 1)) // 锁状态空闲时,尝试直接更新
          setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程为独占锁的拥有者
     else // 若失败,走正常流程获取锁
          acquire(1);
     }
}

compareAndSetState() 就是CAS尝试更新state字段

在ReentrantLock中state字段表示当前线程重入锁的次数,当state为0时候,表示锁是空闲的。

compareAndSetState(0, 1) 表示当state是0,直接更新为1,即空闲时候,直接CAS上锁;线程如果更新成功了,setExclusiveOwnerThread()设置当前线程为锁的占有者;

如果失败了,走acquire()获取锁。

// AQS.java
public final void acquire(int arg) {
   
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

AQS中已经实现了FIFO队列的各种操作,子类只需要重写tryAcquire()tryRelease()两个方法;

tryAcquire()尝试获取锁,在NonfairSync中实现,失败就addWaiter()新生成一个wait结点,并加入等待队列;

acquireQueued() 表示在加入队列以后,阻塞挂起,被唤醒后再次获取资源,获取失败再次阻塞;

要是失败了就只能selfInterrupt()中断了。

// NonfairSync
protected final boolean tryAcquire(int acquires) {
   
	// 直接调用AQS的nonfairTryAcquire
     return nonfairTryAcquire(acquires);
}
// AQS.java
final boolean nonfairTryAcquire(int acquires) {
   
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值