AQS
什么是AQS
JUC包下的大多数同步器的实现都需要围绕等待队列、条件队列、独占获取、共享获取等基础行为,而这些行为正是由AQS来实现的。AQS全称是AbstractQueuedSynchronizer,意为抽象队列同步器。
JDK中提供的大多数同步器,例如Lock、Barrier、Latch等,一般都是通过一个内部类Sync来继承AQS,将同步器所有调用都映射到Sync对应的方法上来实现的。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7M7dDvN-1650992715153)(E:\file\学习\课后总结\并发编程\assert\sub-AQS.png)]
-
AQS特性
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
-
内部属性state
- 定义:volatile in state
- 表示资源的可用状态
- 访问方式
- setState
- getState
- compareAndSwapState
-
资源共享方式
- EXCLUSIVE独占,也就是只有一个线程可以执行,例如ReentrantLock
- SHARED共享,多个线程可以同时执行,例如Semaphore/CountDownLatch
-
队列
- 同步等待队列
- 用于维护获取锁失败时入队的线程
- 条件等待队列
- 调用await时释放锁,并将该线程加入到条件等待队列,调用signal(All)唤醒时会把条件队列中的线程节点移动到同步队列中,等待再次获得锁
- 同步等待队列
-
队列中节点状态
-
0,初始化状态,表示当前节点在sync队列中,等待获取锁
-
1,CANCELLED,表示当前线程被取消
-
-1,SIGNAL,表示表示当前节点的后继节点包含的线程要运行,也就是unpark
-
-2,CONDITION,表示当前节点在条件队列中
-
-3,PROPAGATE,表示当前场景下后续的acquiredShared可以执行
-
不同的自定义同步器竞争共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
AQS 依赖CLH同步队列来完成同步状态的管理:
- 当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程
- 当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
- 通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列)
条件等待队列
AQS中条件队列是使用单向链表保存的,用nextWaiter来连接:
- 调用await方法阻塞线程;
- 当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)
Condition
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UlxBOx6W-1650992715156)(E:\file\学习\课后总结\并发编程\assert\condition.png)]
- 调用Condition#await方法会释放当前持有的锁,然后阻塞当前线程,同时向Condition队列尾部添加一个节点,所以调用Condition#await方法的时候必须持有锁
- 调用Condition#signal方法会将Condition队列的首节点移动到阻塞队列尾部,然后唤醒因调用Condition#await方法而阻塞的线程(唤醒之后这个线程就可以去竞争锁了),所以调用Condition#signal方法的时候必须持有锁,持有锁的线程唤醒被因调用Condition#await方法而阻塞的线程
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
log.debug(Thread.currentThread().getName() + " 开始处理任务");
//会释放当前持有的锁,然后阻塞当前线程
condition.await();
log.debug(Thread.currentThread().getName() + " 结束处理任务");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
lock.lock();
try {
log.debug(Thread.currentThread().getName() + " 开始处理任务");
Thread.sleep(2000);
//唤醒因调用Condition#await方法而阻塞的线程
condition.signal();
log.debug(Thread.currentThread().getName() + " 结束处理任务");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
}
}
ReentranrLock
基本介绍
ReentrantLock是一种基于AQS的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于Synchronized,是一种互斥锁,可以保证线程安全。
相较Synchronized,它有以下特点:
- 可中断
- 可设置超时时间
- 可支持多个条件变量
- 可设置为公平锁
- 与Synchronized一样,都是可重入锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8WGIGfG-1650992715159)(E:\file\学习\课后总结\并发编程\assert\ReentrantLock-sync.png)]
和Synchronized的区别
- Synchronized是JVM层级的锁实现,而ReentrantLock是JDK层级的锁实现
- Synchronized无法在代码中判断锁状态,而ReentrantLock可以通过isLocked来判断
- Synchronized是非公平锁,而ReentrantLock既可以是公平锁也可以是非公平锁
- Synchronized不可以被中断,而ReentrantLock可以通过lockInterruptibly方法中断
- 发生异常时,Synchronized可以自动释放锁,而ReentrantLock需要在finally块中通过unlock显示的释放锁
- ReentrantLock获取锁的方式有很多,例如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
- synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;
使用
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock(true); //公平锁
//加锁
lock.lock();
try {
//临界区
} finally {
// 解锁
lock.unlock();
可重入
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
可中断
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
try {
lock.lockInterruptibly();
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("t1等锁的过程中被中断");
}
}, "t1");
lock.lock();
try {
log.debug("main线程获得了锁");
t1.start();
//先让线程t1执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
log.debug("线程t1执行中断");
} finally {
lock.unlock();
}
}
}
锁超时
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
// 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
if (!lock.tryLock()) {
log.debug("t1获取锁失败,立即返回false");
return;
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
try {
log.debug("main线程获得了锁");
t1.start();
//先让线程t1执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
超时失败
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
//超时
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("等待 1s 后获取锁失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
try {
log.debug("main线程获得了锁");
t1.start();
//先让线程t1执行
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
公平锁
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(true); //公平锁
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入" + i).start();
}
}
条件变量
public class ReentrantLockDemo6 {
private static ReentrantLock lock = new ReentrantLock();
private static Condition cigCon = lock.newCondition();
private static Condition takeCon = lock.newCondition();
private static boolean hashcig = false;
private static boolean hastakeout = false;
//送烟
public void cigratee(){
lock.lock();
try {
while(!hashcig){
try {
log.debug("没有烟,歇一会");
cigCon.await();
}catch (Exception e){
e.printStackTrace();
}
}
log.debug("有烟了,干活");
}finally {
lock.unlock();
}
}
//送外卖
public void takeout(){
lock.lock();
try {
while(!hastakeout){
try {
log.debug("没有饭,歇一会");
takeCon.await();
}catch (Exception e){
e.printStackTrace();
}
}
log.debug("有饭了,干活");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo6 test = new ReentrantLockDemo6();
new Thread(() ->{
test.cigratee();
}).start();
new Thread(() -> {
test.takeout();
}).start();
new Thread(() ->{
lock.lock();
try {
hashcig = true;
//唤醒送烟的等待线程
cigCon.signal();
}finally {
lock.unlock();
}
},"t1").start();
new Thread(() ->{
lock.lock();
try {
hastakeout = true;
//唤醒送饭的等待线程
takeCon.signal();
}finally {
lock.unlock();
}
},"t2").start();
}
}