ReentrantLock和AQS关联和源码解析

前言

java中的同步类(Lock,Semaphore,CountDownLatch)都是基于AbstractQueuedSynchronizer(AQS)。AQS是一种提供了原子式管理同步状态,阻塞和唤醒线程功能以及队列模型的简单框架。

1 ReentrantLock

1.1 ReentrantLock特性概览

ReentrantLock是可重入锁,指的是一个线程能够对一个临界资源重复加锁。

1.1.1 ReentrantLock和synchronized的比较

在这里插入图片描述
下面通过伪代码,进行更加直观的比较

// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {
   }
// 2.用于对象
synchronized (object) {
   }
// 3.用于方法
public synchronized void test () {
   }
// 4.可重入
for (int i = 0; i < 100; i++) {
   
	synchronized (this) {
   }
}
// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
   
	// 1.初始化选择公平锁、非公平锁
	ReentrantLock lock = new ReentrantLock(true);
	// 2.可用于代码块
	lock.lock();
	try {
   
		try {
   
			// 3.支持多种加锁方式,比较灵活; 具有可重入特性
			if(lock.tryLock(100, TimeUnit.MILLISECONDS)){
    }
		} finally {
   
			// 4.手动释放锁
			lock.unlock()
		}
	} finally {
   
		lock.unlock();
	}
}

ReentrantLock与AQS的关联

通过过上文我们已经了解,ReentrantLock支持公平锁和非公平锁(关于公平锁和非公平锁的原理分析,可参考java锁,并且ReentrantLock的底层就是由AQS来实现的。那么ReentrantLock是如何通过公平锁和非公平锁与AQS关联起来呢? 我们着重从这两者的加锁过程来理解一下它们与AQS之间的关系

非公平锁源码的加锁流程如下:

// java.util.concurrent.locks.ReentrantLock#NonfairSync

// 非公平锁
static final class NonfairSync extends Sync {
   
	...
	final void lock() {
   
		if (compareAndSetState(0, 1))
			setExclusiveOwnerThread(Thread.currentThread());
		else
			acquire(1);
		}
  ...
}

这段代码的含义:

  • 若通过cas设置变量State(同步状态)成功,也就是获得锁成功,则将当前线程设置为独占线程。
  • 若通过cas设置变量State(同步状态)失败,也就是获得锁失败,则进入acquire进行后续处理。

第一步很好理解,但第二步获取锁失败后,后续的处理策略是怎么样的呢?这块可能会有以下思考:

某个线程获取锁失败的后续流程是什么呢?有以下两种可能:
(1) 将当前线程获锁结果设置为失败,获取锁流程结束。这种设计会极大降低系统的并发度,并不满足我们实际的需求。所以就需要下面这种流程,也就是AQS框架的处理流程。

(2) 存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。

  • 对于问题1的第二种情况,既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

  • 处于排队等候机制中的线程,什么时候可以有机会获取锁呢?

  • 如果处于排队等候机制中的线程一直无法获取锁,还是需要一直等待吗,还是有别的策略来解决这一问题?

带着非公平锁的这些问题,再看下公平锁源码中获锁的方式:

// java.util.concurrent.locks.ReentrantLock#FairSync

static final class FairSync extends Sync {
   
  ...  
	final void lock() {
   
		acquire(1);
	}
  ...
}

看到这块代码,我们可能会存在这种疑问:Lock函数通过Acquire方法进行加锁,但是具体是如何加锁的呢?

结合公平锁和非公平锁的加锁流程,虽然流程上有一定的不同,但是都调用了Acquire方法,而Acquire方法是FairSync和UnfairSync的父类AQS中的核心方法。

对于上边提到的问题,其实在ReentrantLock类源码中都无法解答,而这些问题的答案,都是位于Acquire方法所在的类AbstractQueuedSynchronizer中,也就是本文的核心——AQS

2. AQS

首先,我们通过下面的架构图来整体了解一下AQS框架:
在这里插入图片描述
下面我们会从整体到细节,从流程到方法逐一剖析AQS框架,主要分析过程如下:
在这里插入图片描述

2.1 原理概述

AQS的核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态,如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配,这个机制主要是由一个双向队列(FIFO),AQS是通过将每天请求资源的线程封装成一个节点来实现锁的分配。节点代码如下:

static final class Node {
   
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    static final int CANCELLED =  1;   //取消状态
    static final int SIGNAL    = -1;  //后续线程需要释放
    static final int CONDITION = -2; //条件状态
    static final int PROPAGATE = -3;//当前线程处在SHARED情况下,该字段才会使用
    volatile int waitStatus;//当前节点的状态
    volatile Node prev;//前一个节点
    volatile Node next;//后一个节点
    volatile Thread thread;//当前线程
    Node nextWaiter;//指向下一个处于CONDITION状态的节点
    private transient volatile Node head;   
    private transient volatile Node tail;
    private volatile int state;

主要原理图如下:
在这里插入图片描述

AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。

2.1.2 同步状态State

在了解数据结构后,接下来了解一下AQS的同步状态——State。AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。

下面提供了几个访问这个字段的方法:

<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值