从源码视角实现一个Mutex认识AQS

192 阅读3分钟

前言

阅读本文的时候,希望是有一定阅读源码的能力跟着思路从源码中探索

什么是AQS

AQS(AbstractQueueSynchronizer)队列同步器,用来构建锁或者其他同步组件的基础框架,简单理解就是一个队列

AQS中一共就两个核心点,这两点是实现大部分同步需求的基础

  • int成员变量state表示同步状态,state = 0表示可以获取锁,state > 0表示有线程在占有锁
  • 内置的FIFO队列完成资源获取线程的排队工作

Synchronized是通过对象头指针及标志位等信息来判断当前锁是否处于同步状态,通过下图直观的感受锁的状态

image.png 反观AQS是通过一个int变量state判断是否处于同步状态,这里不需要关注太多,往下看即可

image.png

同步队列:

image.png

同步队列加入节点的过程(排队等待锁):

image.png

节点获取锁的过程(上一个节点释放锁,下一个节点获得锁的使用权):

image.png

如果感兴趣阅读源码的话,可以跟着下图的阅读顺序去看源码,以非公平锁和公平锁为例

读lock()源码顺序(以ReentrantLock中的NonfairSync和FairSync为例):

image.png

读unLock()源码顺序(以ReentrantLock为例):

image.png

读源码的时候大家可以发现所谓的公平锁和非公平锁的区别是 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的控制

image.png

思考 - 获取到同步状态的节点什么时候移出队列

一开始我以为,当线程用完锁的时候,将锁释放了,不就代表着从同步队列中移出了吗

在追溯源码的时候发现,并不是在锁释放的时候移出队列

而是在同步状态被后继节点获取之后,上一个获取同步状态的节点才被移出队列,具体我们可以从这一段源码看到 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 的操作