前言
阅读本文的时候,希望是有一定阅读源码的能力跟着思路从源码中探索
什么是AQS
AQS(AbstractQueueSynchronizer)队列同步器,用来构建锁或者其他同步组件的基础框架,简单理解就是一个队列
AQS中一共就两个核心点,这两点是实现大部分同步需求的基础
- int成员变量state表示同步状态,state = 0表示可以获取锁,state > 0表示有线程在占有锁
- 内置的FIFO队列完成资源获取线程的排队工作
Synchronized是通过对象头指针及标志位等信息来判断当前锁是否处于同步状态,通过下图直观的感受锁的状态
反观AQS是通过一个int变量state来判断是否处于同步状态,这里不需要关注太多,往下看即可
同步队列:
同步队列加入节点的过程(排队等待锁):
节点获取锁的过程(上一个节点释放锁,下一个节点获得锁的使用权):
如果感兴趣阅读源码的话,可以跟着下图的阅读顺序去看源码,以非公平锁和公平锁为例
读lock()源码顺序(以ReentrantLock中的NonfairSync和FairSync为例):
读unLock()源码顺序(以ReentrantLock为例):
读源码的时候大家可以发现所谓的公平锁和非公平锁的区别是 hasQueuedPredecessors() 这个函数和lock()的前置CAS
也就是打不打声招呼的问题,公平锁会礼貌的问一下,有人了就去排队,非公平锁不会问,直接插队,没有抢到位置就去排队
自定义实现一个简单的Lock(非可重入)
注意我们拿不拿得到锁,是看state是否处于同步状态,认识这一点就能入门AQS
当State == 0 的时候意味着此时可以获取锁,State == 1 意味着锁被占用了
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
public int getQueueNodeCount() { return sync.getQueueLength(); }
}
测试一下:
class MutexExample implements Runnable {
Mutex mutex = new Mutex();
@Override
public void run() {
mutex.lock();
System.out.println("线程" + Thread.currentThread().getName() + "开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "结束, 当前队列等待数量:" + mutex.getQueueNodeCount());
mutex.unlock();
}
public static void main(String[] args) {
MutexExample example = new MutexExample();
Thread t1 = new Thread(example,"t1");
Thread t2 = new Thread(example,"t2");
Thread t3 = new Thread(example,"t3");
Thread t4 = new Thread(example,"t4");
Thread t5 = new Thread(example,"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
重点在于我们自己实现的Lock对State的控制
思考 - 获取到同步状态的节点什么时候移出队列
一开始我以为,当线程用完锁的时候,将锁释放了,不就代表着从同步队列中移出了吗
在追溯源码的时候发现,并不是在锁释放的时候移出队列
而是在同步状态被后继节点获取之后,上一个获取同步状态的节点才被移出队列,具体我们可以从这一段源码看到 p.next = null
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
当node节点的线程获取到同步状态之后,上一个获取了同步状态的线程p此时才进行 p.next = null 的操作