抽象同步队列—AQS
AbstractQueuedSynchronizer抽象同步队列,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。如常见的ReentrantLock、CountDownlatch、semaphore等。
state变量
AQS最重要的一个变量——状态信息变量state
,该变量根据具体的应用场景和实现类有不同的意义。例如,在ReentrantLock
中,state
表示当前线程获取锁的重入次数,CountDownlatch
中表示计数器的值,semapho
re
中表示的是信号量的可用数量。
AQS中的核心方法有:
void acquire(int arg)
:用于获取独占资源,arg
为申请数量,该方法内部是通过调用bolean tryAcquire(int arg)
来获取资源的,在AQS中并未实现该方法,方法的具体实现是交给其子类去实现的,子类根据具体的业务需求设计相应的资源操作逻辑。void acquireInterruptibly(int arg)
:该方法与acquire()
方法的区别在于,该方法对会中断进行相应,发生中断时会抛出InterruptedException
异常,而后者不响应中断。boolean release(int arg)
:该方法内部通过调用tryRelease(int arg)
方法释放资源,tryRelease
方法也是需要子类去实现的。void acquireShared(int arg)
:用于获取共享资源,内部调用int tryAcquireShared(int arg)
方法实现。void acquireSharedInterruptibly(int arg)
:响应中断的方式。boolean releaseShared(int arg)
:该方法内部通过调用tryReleaseShared(int arg)
方法释放资源,tryRelease
方法也是需要子类去实现的。
AQS阻塞队列
在AQS中维持了一个双向队列,该队列采用的数据结构是双向链表,链表的结点保存的是被阻塞的线程的信息,包括当前线程结点的前驱和后继结点的指针、当前线程竞争的资源类型(独占资源用EXCLUSIVE
标识,共享资源用SHAREAD标识)以及线程在哪个条件队列中等待,等等。线程中请求资源失败后会为当前线程创建一个新的结点插入到阻塞队列中,当竞争资源释放时,队列中的线程会再次竞争资源。
AQS条件变量
AQS中包含一个内部类ConditionObject
,该类用于实现条件等待与唤醒,该类中提供了await()
、signal()
和signalAll()
方法实现了类似synchronized
中的wait()
和notify()
方法的功能。该类的实例被称为条件变量,一个锁可以对应多个条件变量,每个条件变量维持一个条件队列(单向链表),在条件队列中的线程需要等待条件变量的唤醒。
通过条件变量调用await()
方法的线程进入的是对应的条件队列,只有竞争资源失败的线程才会进入AQS同步阻塞队列中。
自定义不可重入锁
package sas.LearnJava.JUCTest;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自定义不可重入锁
* 底层通过AQS实现,需要重写acquire()、tryAcquire()等方法
*/
public class NonReentrantLock implements Lock, Serializable {
private final Sync sync = new Sync();
@Override
public void lock() {
// 通过调用acquire方法实现
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
// 可中断方法是在不可中断方法的基础上加入了中断检查
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@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();
}
// 内部辅助类
private static class Sync extends AbstractQueuedSynchronizer {
/**
* 该方法的抽象功能是用来判断当前的锁是共享锁还是独占锁的,
* 不过在这里设计的是一种不可重入锁,是一种独占锁,
* 因此具体是现实时它的功能用来判断当前锁是否已经被获取
*/
protected boolean isHeldExclusively() {
return getState() == 1; // getState()时AQS提供的方法
}
// AQS中没有实现tryAcquire()方法,因此必须重写该方法
public boolean tryAcquire(int acquire) {
assert acquire == 1; // 由于是不可重入锁,state取值为0或1,在获取时必须为1
// 使用CAS更新state的值,如果成功表明获取锁成功,将锁的拥有者设置为当前线程,返回true
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 重写tryRelease()方法,尝试释放资源
protected boolean tryRelease(int releases) {
assert releases == 1;
if (getState() == 0) { // 锁未被任何线程获取
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0); // setState为普通方法,需要保证资源的拥有者才能释放资源
return true;
}
// 提供生成条件变量的接口
Condition newCondition() {
return new ConditionObject();
}
}
}
利用自定义锁实现生产者消费者线程同步
package sas.LearnJava.JUCTest;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;
public class ProducerAndConsumer {
final static NonReentrantLock lock = new NonReentrantLock();
// 一个锁可以有多个条件变量
final static Condition notFull = lock.newCondition();
final static Condition notEmpty = lock.newCondition();
final static Queue queue = new LinkedBlockingQueue<>(); // 模拟缓冲池
final static int queueSize = 10;
public static void main(String[] args) {
Thread producer = new Thread(()->{
lock.lock();
try {
// 队列已满则等待
while (queue.size() == queueSize) {
System.out.println("数据已放满,等待消费者取出数据");
notEmpty.await(); // 条件变量调用await方法,当前线程进入相应的条件队列中
}
// 向队列中添加元素
queue.add("apple");
System.out.println("生产者放入数据");
// 唤醒消费者线程
Thread.sleep(1000);
notFull.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
Thread consumer = new Thread(()->{
lock.lock();
try {
// 队列已满则等待
while (0 == queue.size()) {
System.out.println("数据为空,等待生产者放入数据");
notFull.await(); // 条件变量调用await方法,当前线程进入相应的条件队列中
}
// 去除数据
String friut = queue.poll();
System.out.println("消费者取出数据: " + friut);
// 唤醒生产者线程
notEmpty.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
// 启动线程
consumer.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
producer.start();
}
}
内容来自《Java并发编程之美》