ReentrantLock原理以及简单手写ReentrantLock
最近在以ReentrantLock为例研究aqs的源码实现,以下介绍一下自己的心得,以及简单手写一个ReentrantLock锁类,加深对ReentrantLock的原理了解。
ReentrantLock原理
ReentrantLock是java中最常见的锁,ReentrantLock是利用AQS队列+CAS+
LockSupport来实现的,它是一种独占锁、可重入锁,它支持公平锁和非公平锁模式。
AbstractQueuedSynchronizer(AQS),类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
了解AQS实现原理有助于我们理解其他并发工具类。本文不注重探究ReentrantLock源码实现原理(下篇文章再来探究源码),以手写锁类来揭示
ReentrantLock实现思想,尽量保持方法名与源码方法一致,方便同学们对照源码学习,本文只是简单实现,源码比较复杂的设计就一一略过了,有兴趣的同学可以参照源码研究。
简单手写ReentrantLock
package com.argus.aqs.aqsdemo;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* @description: 手写reentrantlock
* @author: argus
* @date: Created in 2023/3/15 15:46
* @version: 1.0.0
* @modified By:
*/
public class ArgusLock {
private final ArgusSync sync;
public ArgusLock(){
sync = new nonfailArgusSync();
}
public ArgusLock(boolean fail){
sync= fail ? new failArgusSync() : new nonfailArgusSync();
}
public void lock(){
sync.lock();
}
public void unlock(){
sync.release(1);
}
static abstract class ArgusSync{
//cas 控制锁状态 0没有线程占用 1 被线程专用
private AtomicInteger atomicInteger = new AtomicInteger(0);
//没有获取锁的线程存放队列
private ConcurrentLinkedDeque<Thread> concurrentLinkedDeque = new ConcurrentLinkedDeque<>();
//当前锁持有线程
private transient Thread exclusiveOwnerThread;
private transient Thread headThread;
protected void setExclusiveOwnerThread(Thread thread){
exclusiveOwnerThread = thread;
}
protected Thread getExclusiveOwnerThread(){
return exclusiveOwnerThread;
}
protected int getAtomicInteger(){
return atomicInteger.get();
}
protected boolean dequeIsEmpty(){
return concurrentLinkedDeque.isEmpty();
}
protected Thread getHeadThread(){
return headThread;
}
protected void setHeadThread(Thread thread){
headThread = thread;
}
/**
* cas获取锁的状态
* @param expect
* @param update
* @return
*/
protected boolean compareAndSet(int expect, int update){
return atomicInteger.compareAndSet(expect,update);
}
/**
* 加锁
*/
abstract void lock();
/**
* 释放锁
*/
public void release(int releases){
if(tryRelease(releases)){
//释放锁成功 将等待线程唤醒
if(!concurrentLinkedDeque.isEmpty()){
//将头部元素唤醒 非公平锁可以检索所有等待线程 全部唤醒重新争抢资源 这里按照
//ReentrantLock方式非公平锁直接竞争资源 加入等待队列后 排队竞争
Thread thread = concurrentLinkedDeque.pollFirst();
if(null != thread){
//设置头部节点 公平锁自旋判断需要
setHeadThread(thread);
LockSupport.unpark(thread);
}
}
}
}
/**
* 每次释放锁 将state减1 直至等于0 代表锁不被占有
* @param releases
* @return
*/
public boolean tryRelease(int releases){
int state = getAtomicInteger();
int c = state - releases;
boolean free = false;
//必须持有锁线程才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
if(c == 0){
//释放锁成功
free = true;
setExclusiveOwnerThread(null);
}
if (c < 0) {
throw new Error("Maximum lock count exceeded");
}
//修改原子类的值 并不是直接改为0 而是每次减1 重入需要多次释放锁
compareAndSet(state,c);
return free;
}
/**
* 获取锁
* @param arg
*/
protected void acquire(int arg){
if(!tryAcquire(arg)){
//再次尝试获取锁失败或者也不是线程重入 加入等待队列并自旋 线程阻塞
acquireQueued(arg);
}
}
/**
* 尝试获取锁
* @param arg
* @return
*/
abstract boolean tryAcquire(int arg);
/**
* 线程获取锁失败 自旋并加入队列 阻塞
* @param arg
*/
private void acquireQueued(int arg){
try {
boolean failed = true;
for (;;){
//防止并发情况下 新入线程抢先获取到锁
if(tryAcquire(arg)){
//获取到锁 直接跳出循环
failed = false;
return;
}
//获取失败加入等待队列
concurrentLinkedDeque.add(Thread.currentThread());
//阻塞线程
LockSupport.park();
}
}finally {
//增加保险策略 如果有意外情况将该线程移除
if(false){
cancleAcquire(Thread.currentThread());
}
}
}
/**
* 将线程从异常中移除
* @param thread
*/
private void cancleAcquire(Thread thread){
concurrentLinkedDeque.remove(thread);
}
}
static class failArgusSync extends ArgusSync{
@Override
void lock() {
acquire(1);
}
@Override
boolean tryAcquire(int arg) {
Thread thread = Thread.currentThread();
int state = getAtomicInteger();
//锁没有被占有
if(state == 0){
//判断队列是否有元素 再次尝试获取锁
//俩种情况 第一次获取锁 直接判断是否有等待线程 第二种 判断是否是唤醒的线程
if((dequeIsEmpty() || thread == getHeadThread()) && compareAndSet(0,1)){
setExclusiveOwnerThread(thread);
//删除头部节点
setHeadThread(null);
return true;
}
}else if(thread == getExclusiveOwnerThread()){
//添加锁重入机制
int newState = state+arg;
//加上保护措施
if (newState < 0) {
throw new Error("Maximum lock count exceeded");
}
//将锁次数加1
compareAndSet(state,newState);
return true;
}
return false;
}
}
static class nonfailArgusSync extends ArgusSync{
@Override
void lock() {
//直接获取锁
if(compareAndSet(0,1)){
//获取到锁 将当前线程设置为持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
}else{
acquire(1);
}
}
@Override
boolean tryAcquire(int arg) {
Thread thread = Thread.currentThread();
int state = getAtomicInteger();
//锁没有被占有
if(state == 0 ){
//再次尝试获取锁
if(compareAndSet(0,1)){
setExclusiveOwnerThread(thread);
return true;
}
}else if(thread == getExclusiveOwnerThread()){
//添加锁重入机制
int newState = state+arg;
//加上保护措施
if (newState < 0) {
throw new Error("Maximum lock count exceeded");
}
//将锁次数加1
compareAndSet(state,newState);
return true;
}
return false;
}
}
}
主要类和方法
ArgusLock:自定义锁类 里面定义了静态内部类ArgusSync以及加锁lock()、释放锁unlock()的方法。提供俩个构造器,一个无参构造,默认是非公平锁,一个有参构造 当参数为true是公平锁,false非公平锁,与ReentrantLock用法一样。

ArgusSync:静态抽象内部类,相当于ReentrantLock的Sync类,Sync类是通过继承AbstractQueuedSynchronizer,通过Unsafe提供的硬件级别的原子操作来实现CAS操作,这里直接通过AtomicInteger原子类来实现,AQS内部维护了一个虚拟的双向链表FIFO队列,每一个等待的线程构成一个Node节点,这里直接用并发包下的concurrentLinkedDeque替代。
atomicInteger: cas 控制锁状态 0没有线程占用 1 被线程专用
concurrentLinkedDeque:没有获取锁的线程存放队列
exclusiveOwnerThread:当前锁持有线程
headThread:头部节点(AQS中的双端链表的头结点是一个无参构造函数的头结点,头部节点的后置节点才是等待唤醒的线程节点,这里方便后续线程自旋唤醒判断逻辑,保留头部节点)

failArgusSync:公平锁类 继承ArgusSync
nonfailArgusSync:非公平锁类 继承ArgusSync
加锁、释放锁流程
加锁:ArgusSync.lock(),里面调用了ArgusSync的lock()

公平锁实现:调用ArgusSync.acquire(),
可以看到先调用tryAcquire(),初次获取锁状态(非公平、公平锁实现不同),如果获取失败加入等待队列,acquireQueued()。

公平锁获取锁,先判断锁状态,如果为0 代表没有线程占有锁,下面有俩种情况等待线程为空,或者当前线程为头结点(自旋、阻塞被唤醒之后),直接获取锁状态compareAndSet(),如果成功代表当前线程获取到所锁,将持有线程设为当前线程,同时将头部节点标识删除(针对第二种情况)。如果锁被占有时要判断是否是线程重入(锁的重入性逻辑),如果持有线程是当前线程代表锁重入,不需要再次获取锁,直接将当前锁状态state加1。

当线程没有获取到锁时,加入等待列,通过自旋一直获取锁,获取到锁直接跳出自旋,没有获取到锁,加入等到队列末尾,防止自旋次数导致浪费cpu资源,并将线程阻塞。cancleAcquire()防止意外情况下将线程移除等待队列(在ReentrantLock设计中,cancleAcquire方法是将当前node的waitstatus设为-1即取消状态,感兴趣的同学可以去研究下源码,本人在研究的时候发现cancleAcquire方法好像不会触发,不知道是不是作者的一种保险策略)。
非公平锁:与公平锁大致相同

在加锁的时候直接先获取锁的状态,获取失败在调用acquire()

非公平锁的tryAcquire(),先是直接获取一次锁状态,像其他重入逻辑类似
释放锁
非公平锁、公平锁释放锁的原理一致

必须锁持有线程才能释放锁,获取锁的state每次减1(不是直接修改为0,因为重入需要多次释放锁),释放锁成功后清除当前持有线程。

释放锁成功后如果等待队列中有等待线程,直接获取头部线程(pollFirst检索双端队列的头部元素并删除,不清楚的同学可以看下ConcurrentLinkedDeque的相关api),并将线程unpark(),加入自旋获取锁状态。
总结
简单手写ReentrantLock实现原理只是为了加深自己在研究源码时的理解,参考了ReentrantLock源码 但是设计也比较粗糙,比如AQS是怎么维护双端队列的node节点、删除状态为CANCELLED的节点、shouldParkAfterFailedAcquire方法的实现、addWaiter添加末尾节点、cancelAcquire等都只是简单化处理。感兴趣的同学可以自己去研究下源码,这里只是提供了一种简便思想方便大家理解ReentrantLock源码。理解有限,大佬勿喷。
本文介绍了ReentrantLock的原理,它是基于AQS、CAS和LockSupport实现的可重入锁,支持公平和非公平模式。通过手写一个简单的ReentrantLock类,阐述了加锁、释放锁的流程,以及主要类和方法的设计,帮助读者深入理解ReentrantLock的工作机制。
1万+

被折叠的 条评论
为什么被折叠?



