主要参考文章
https://javadoop.com/2017/06/16/AbstractQueuedSynchronizer/
首先介绍AQS基本原理,然后介绍AQS在ReentrantLock和CountDownLatch上的应用
AQS
概述
AQS全程是AbstractQueuedSynchronizer,顾名思义是一个基于队列的同步器。底层使用LockSupport.park和LockSupport.unPark实现对线程的挂起和唤醒。
如果使用了condition,情况就会稍微有些复杂
以下面代码为例
package com.test.CountDownLatchTest.AQS;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 创建两对线程,每一对线程分为wait和signal两部分,并使用各自的condition进行线程同步
* 2021-07-25
*/
public class testAQS {
public static final Lock lock = new ReentrantLock();
public static final Condition condition1 = lock.newCondition();
public static final Condition condition2 = lock.newCondition();
public static void main(String[] args) throws Exception {
Thread thread1_wait = new Thread(() -> {
lock.lock();
try {
System.out.println("thread1_wait begin");
condition1.await();
System.out.println("thread1_wait end");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "thread1_wait");
Thread thread2_wait = new Thread(() -> {
lock.lock();
try {
System.out.println("thread2_wait begin");
condition2.await();
System.out.println("thread2_wait end");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "thread2_wait");
Thread thread1_signal = new Thread(() -> {
lock.lock();
try {
System.out.println("thread1_signal begin");
Thread.sleep(10000);
condition1.signal();
System.out.println("thread1_signal end");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 断点1
}
}, "thread1_signal");
Thread thread2_signal = new Thread(() -> {
lock.lock();
try {
System.out.println("thread2_signal begin");
Thread.sleep(1000);
condition2.signal();
System.out.println("thread2_signal end");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//断点2
}
}, "thread2_signal");
thread2_wait.start();
Thread.sleep(100);
thread1_wait.start();
Thread.sleep(100);
thread1_signal.start();
Thread.sleep(100);
thread2_signal.start();
}
}
//AQS中的函数
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
首先理清一下四个线程的时序图
每一个condition有一个队列,用于存储调用condition.await()的线程。当其他线程调用condition.signal()时,AQS会讲node从condition队列中移动到阻塞队列中
可以通过添加断点证明上述时序图的正确性。下面举几个例子
“8 运行结束,释放锁”,在"断点1"处添加断点。可以看到阻塞队列中有三个节点,第一个是头节点,第二个是thread2_signal,第三个是thread1_wait。证明了第5步和第7步的顺序问题。
“12 运行结束,释放锁”,在"断点2"处添加断点。可以看到阻塞队列中有三个节点,第一个是头节点,第二个是thread1_wait,第三个是thread2_wait。证明了第7步和第11步的顺序问题。
由于程序中存在Thread.sleep函数,建议每次只测试一个断点
源码
待续。可参考文章开始处的参考链接。
ReentrantLock
待续。AQS的排它锁
CountDownLatch
待续。AQS的共享锁
其他
ReentrantLock 公平与非公平
在非公平的状态下,新来的线程在入队之前会尝试抢一次锁,如果失败了就会乖乖进入队列,一旦进入队列是不能再次出来抢的,只能等待队列一个一个地执行完毕(进入到了队列中的线程就公平了,先进入队列的一定比后进入的先获取到锁)。所谓不公平是指新来的线程会不会在入队之前尝试「野蛮」地抢锁,公平的时候是不会,但是非公平的时候是会的。