抽象队列同步器 AQS
概念
1)AQS是AbstractQueuedSynchronizer的简称,即抽象队列同步器
抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现
队列:使用先进先出(FIFO)的队列存储数据
同步:实现了同步的功能
2)AQS 就是起到了一个抽象、封装的作用,运用模板方法模式,将一些排队、入队等通用方法提供出来,具体加锁时机、入队时机等都需要实现类自己控制
3)AQS 常见的实现类有 ReentrantLock、ReentrantReadWriteLock 等
数据结构
维护一个共享状态(state)和一个先进先出的同步队列,队列采用双向链表结构,节点包含线程的引用、等待状态以及前驱和后继节点的指针
state 用 volatile 修饰,表示当前资源的状态
当线程尝试获取资源失败时,会被加入到 AQS 的同步队列中
重入锁 ReentrantLock
概念
1)线程获取锁时,若已获取锁的线程是当前线程,则直接再次获取成功
2)由于锁被获取 n 次,那么只有锁在被释放同样的 n 次之后,该锁才算是完全释放成功
基本使用
创建锁时,构造函数传入true为公平锁,不传为非公平锁
使用ReentrantLock时,锁必须在 try 代码块开始之前获取。必须在 finally 块内释放锁
(若在 try 块内加锁,加锁前发生了异常,在 finally 块内会对未上锁的锁进行释放操作)
public class ReentrantLockTest {
private static final ReentrantLock lock = new ReentrantLock();//申请了静态的重入锁
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();//在try块之前加锁
try {
count++;
} finally {
lock.unlock();//在finally块中释放锁
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}
ReentrantLock 与 Synchronized
相同点:都用来实现同步,都是可重入锁
区别:
Synchronized:
1)是 Java 内置的关键字,实现基本的同步机制
2)不支持超时,非公平,不可中断,不支持多条件
3)自动解锁
ReentrantLock:
1)是 JUC 类库提供的,
2)支持设置超时时间,支持公平锁与非公平锁,可中断,支持多条件判断,可以避免死锁
3)需要手动解锁,
选择:
一般情况下用Synchronized,简单
复杂的情况用ReentrantLock,灵活,功能多
性能:Synchronized经过发展有四种等级的锁,性能与ReentrantLock相差不多
读写锁 ReentrantReadWriteLock
特性
1.读锁是共享锁,写锁是独享锁
2.支持公平锁/非公平锁、重入性
3.锁降级:
持有写锁的线程在释放写锁前获取读锁,从而在写操作完成后不必完全释放锁
(不支持锁升级)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
writeLock.lock(); // 获取写锁
try {
// 执行写操作
readLock.lock(); // 获取读锁
} finally {
writeLock.unlock(); // 释放写锁
}
try {
// 执行读操作
} finally {
readLock.unlock(); // 释放读锁
}
原理

1)AQS 中高 16 位表示读状态,低 16 位表示写状态(源码中在释放写锁时,使用当前同步状态state直接减去写状态releases,原因正是写状态是由同步状态的低 16 位表示的)
2)当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败
基本使用
示例:
实现了缓存读写、锁降级
public class CachedData {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Object data;
private boolean cacheValid;
// 检查缓存状态并读取最新数据
public void processCachedData() {
// 获取读锁
rwl.readLock().lock();
if (!cacheValid) {// 缓存失效
// 获取读锁前必须先释放写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 再次检查状态,因为另一个线程可能已经获得了写锁并完成了写操作
if (!cacheValid) {
data = fetchDataFromDatabase();// 读数据库
cacheValid = true;
}
// 锁降级:在释放写锁前先获取读锁
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 释放写锁
}
}
try {
use(data);// 输出数据
} finally {
rwl.readLock().unlock();// 释放读锁
}
}
}
等待通知条件 Condition
概念
1)Condition接口是 Java 并发编程中一个重要的组件,用于线程间的协调和通信
2)通常与锁一起使用,为线程提供等待某个条件变化时通知等待线程的机制
接口

await():线程等待直到被通知或者中断
awaitUninterruptibly():线程等待直到被通知,即使在等待时被中断也不会返回
await(long time, TimeUnit unit):线程等待指定的时间,或被通知,或被中断
awaitNanos(long nanosTimeout):线程等待指定的纳秒时间,或被通知,或被中断
awaitUntil(Date deadline):线程等待直到指定的截止日期,或被通知,或被中断
signal():唤醒一个等待的线程
signalAll():唤醒所有等待的线程
线程阻塞唤醒类 LockSupport
概念
提供了一组阻塞和唤醒线程的静态方法,它不依赖于同步块或特定的锁对象,可以灵活地构建更复杂的同步结构
注意:
void unpark(Thread thread):唤醒一个由park方法阻塞的线程。如果该线程未被阻塞,那么下一次调用park时将立即返回。这允许“先发制人”式的唤醒机制
Synchronized阻塞线程,会使线程进入 BLOCKED 状态,LockSupport会使线程进入 WAINTING 状态
使用
示例:
有 3 个独立的线程,一个只会输出 A,一个只会输出 B,一个只会输出 C,在三个线程启动的情况下,请用合理的方式让他们按顺序打印 ABCABC
public class ABCPrinter {
private static Thread t1, t2, t3;
public static void main(String[] args) {
int times = 2;
t1 = new Thread(() -> {
for (int i = 0; i < times; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(t2);
}
});
t2 = new Thread(() -> {
for (int i = 0; i < times; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(t3);
}
});
t3 = new Thread(() -> {
for (int i = 0; i < times; i++) {
LockSupport.park();
System.out.print("C");
LockSupport.unpark(t1);
}
});
t1.start();
t2.start();
t3.start();
// 主线程稍微等待一下,确保其他线程已经启动并且进入park状态。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动整个流程
LockSupport.unpark(t1);
}
}
