AQS
参考文档
- https://pdai.tech/md/java/thread/java-thread-x-lock-AbstractQueuedSynchronizer.html
AQS简介
AbstractQueuedSynchronizer
- AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的
ReentrantLock
,Semaphore
,其他的诸如ReentrantReadWriteLock
,SynchronousQueue
,FutureTask
等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
AQS 核心思想
AQS核心思想是:
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
- 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS原理
基本概述
AQS 全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
AQS特点
用 state
属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁 ;
getState
- 获取state
状态setState
- 设置state
状态compareAndSetState
- cas 机制设置state
状态
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
提供了基于 FIFO 的等待队列,类似于 Monitor
的 EntryList
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet
子类主要实现这样一些方法 :
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
默认抛出 UnsupportedOperationException
AQS自定义锁
自定义锁
独占锁
独占锁是不可重入锁 , 已经获得锁的话, 再次请求获得锁会导致死锁.
/**
* 独占锁 同步器类
*/
class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 尝试获取锁, 通过状态字段state设置锁
if (compareAndSetState(0, 1)) {
// 加锁, 并设置 owner 为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
// 释放的时候, 因为已经获取独占锁了, 只有当前线程有锁, 所以直接设置状态即可
// state 是volatile修饰, 在此之前对类属性的修改, 都会同步到主存, 并且
setState(0);
return true;
}
/**
* 是否持有独占锁
*
* @return
*/
@Override
protected boolean isHeldExclusively() {
return this.getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
我们通过集成 AbstractQueuedSynchronizer
类来实现独占锁, AbstractQueuedSynchronizer 是使用 state 字段用于控制获取锁
package cn.knightzz.juc.aqs.custom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @author 王天赐
* @title: MyAqsLock
* @projectName hm-juc-codes
* @description: 自定义不可重入锁
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-05 14:27
*/
@SuppressWarnings("all")
public class MyAqsLock implements Lock {
/**
* 独占锁 同步器类
*/
class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 尝试获取锁, 通过状态字段state设置锁
if (compareAndSetState(0, 1)) {
// 加锁, 并设置 owner 为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
// 释放的时候, 因为已经获取独占锁了, 只有当前线程有锁, 所以直接设置状态即可
// state 是volatile修饰, 在此之前对类属性的修改, 都会同步到主存, 并且
setState(0);
return true;
}
/**
* 是否持有独占锁
*
* @return
*/
@Override
protected boolean isHeldExclusively() {
return this.getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private MySync sync = new MySync();
/**
* 加锁, 加锁不成功会进入等待队列
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* 加锁, 但是可以打断
*
* @throws InterruptedException
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.tryAcquire(1);
}
/**
* 尝试加锁 (尝试一次)
*
* @return
*/
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
/**
* 尝试加锁, 带超时
*
* @param time the maximum time to wait for the lock
* @param unit the time unit of the {@code time} argument
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
简单介绍下 :
- 我们需要先自定义MySync, 继承AbstractQueuedSynchronizer类, 这个类实现了一些锁相关的方法
- AbstractQueuedSynchronizer 通过使用一个volatile修饰的state字段+CAS来实现加锁/解锁
测试代码
package cn.knightzz.juc.aqs.custom;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* @author 王天赐
* @title: MyAqsLockTest
* @projectName hm-juc-codes
* @description: 测试自定义锁
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-06 21:47
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.MyAqsLockTest")
public class MyAqsLockTest {
public static void main(String[] args) {
MyAqsLock lock = new MyAqsLock();
new Thread(() -> {
lock.lock();
try {
log.debug("locking...");
sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t1").start();
new Thread(() -> {
lock.lock();
try {
log.debug("locking...");
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t2").start();
}
}