11-2. 源码剖析AQS(AbstractQueuedSynchronizer)-抽象队列同步器

前言:上一节实现了一个自定义锁,并且验证了其功能,这节将从源码出发,解析J.U.C的源码,并引出抽象队列同步器(AQS)。

1 JDK的ReentrantLock

1.1 ReentrantLock

上节我们自己实现了tryLock、lock、unlock三个方法,我们先从这三个方法入手,看JDK的实现跟我们自己的实现有什么异同。

先给出类图:

从类图可以看出,ReentrantLock有三个内部类,而Sync是AbstractQueuedSynchronizer的子类,而Sync有公平和非公平的两个子类。

1.1.1 ReentrantLock#tryLock

我们先捋一捋tryLock应该会有什么样的实现
1)ReentrantLock应该有属性来确定锁是被哪个线程所占用
2)tryLock即是多个线程会同时去修改1)中所提及的属性,修改成功,则说明该线程占有该锁。

1.1.1.1 找到确定线程占用的属性

ReentrantLock只有一个field:Sync-> AbstractQueuedSynchronizer(以下简称为AQS)

点开到AbstractOwnableSynchronizer发现了这个属性

/**
 * The current owner of exclusive mode synchronization.
 */
private transient Thread exclusiveOwnerThread;

1.1.1.2 解析tryLock方法

ReentrantLock#tryLock

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

Sync#nonfairTryAcquire

AbstractQueuedSynchronizer# compareAndSetState

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AbstractOwnableSynchronizer# setExclusiveOwnerThread

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

说明:从nonfairTryAcquire 的if块看来,首先是多个线程来设置AQS的state属性,设置成功则说明拥有该锁,else块用于在当前线程已经拥有该锁时,再继续来获取该锁,重入时增加锁的次数。(注:Exclusive此处应该翻译为互斥的、排他的)

1.1.2 ReentrantLock#lock

ReentrantLock#lock

public void lock() {
    sync.lock();
}

Sync#lock

abstract void lock();

实现类有公平和非公平

公平和非公平的区别在于,抢锁的时候,假设当前线程一占有着锁,线程二和线程三之前已经抢过了,没抢到,然后线程一此时将锁释放掉,会唤醒等待池(锁池)中的线程二、三抢锁,但是此时刚好线程四也来抢了,线程四之前是不在等待池里的,这时候就有可能被后来的线程四抢到了锁,所以这种情况就是非公平的。
而公平和非公平的实现区别就在于,公平的会先去判断当前抢锁的线程是否在等待池中,是的话才让它去抢锁。

NonfairSync# lock

compareAndSetState,当前锁出现多线程争抢的情况下,哪个线程设置state成功,锁就归哪个线程所有,compareAndSetState底层是使用unsafe硬件同步原语来保证只有一个线程能成功,这样来保证原子性的,而对于设置不成功state属性的线程(即抢不到锁的线程,执行acquire(1))
这里还要说明一下compareAndSetState还有一种情况会失败,就是当前线程已经抢到该锁了,然后又需要再锁一次(即重入)

AbstractQueuedSynchronizer# acquire

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 尝试获取锁,获取不到则添加当前线程到等待池
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//Node.EXEXCLUSIVE是null值,若mode为互斥类型的,说明锁的下个等待者为空
        selfInterrupt();
}

tryAcquire(arg):若当前线程重入成功需要记录让state++,说明加锁次数+1

AbstractQueuedSynchronizer# addWaiter

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//有独占模式和共享模式 mode为null意味着独占模式
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//设置tail为当前节点
            pred.next = node;
            return node;
        }
    }
    enq(node);//将设置成功的线程的节点入队 enq->insert into queue
    return node;
}

从addWaiter可以看到,AbstractQueuedSynchronizer是使用链表来作为锁池,存储等待该锁的所有线程,并且把每个新加入争抢的线程保存在链表的尾部。
AbstractQueuedSynchronizer# acquireQueued

蓝色的部分说明没有抢到锁的线程,最后都会进入Park的状态。

AbstractQueuedSynchronizer#release流程

AQS#acquire

1.1.3 ReentrantLock#unlock

ReentrantLock#unlock

public void unlock() {
    sync.release(1);
}

AbstractQueuedSynchronizer# release

AbstractQueuedSynchronizer# tryRelease

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

查看tryRelease的实现

ReentrantLock #Sync# tryRelease

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

tryRelease方法比较简单,就是判断重入的次数是否为0了,若为0则将占有锁的属性设置为null。
回到AbstractQueuedSynchronizer# release,查看unparkSuccessor
AbstractQueuedSynchronizer# unparkSuccessor(从名字上看是unpark继任者)

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

此处主要就是从队列中拿出符合状态的继任者,继续将该线程unpark。

AbstractQueuedSynchronizer#release流程

AQS#release

从tryLock、lock、unlock三个方法可以看出,AQS作为一个抽象类,主要是提供一个模板方法,tryLock、tryRelease都让子类去实现,它不关心你是要怎么锁的,它只关心你获取锁后怎样存储这些争抢锁的线程以及将它们阻塞起来,它还关心释放锁之后找到下一个继任者,并且将下个继任者unpark置为非阻塞。
方法对照:
Lock子类的lock方法<-->AQS的acquire
Lock子类的unlock方法<-->AQS的release

2 JDK的AbstractQueuedSynchronizer

2.1 分析AbstractQueuedSynchronizer继承结构与属性

1)锁池
前面说过抽象队列同步器提供的模板方法,将管理锁池、阻塞线程、释放线程的逻辑都放到了AQS中,AQS中有两个内部类,一个是Node,前面相关的源码已经有说明了,AQS是通过链表来维护这个锁池的。
2)锁持有者
AbstractOwnableSynchronizer中的exclusiveOwnerThread,标志着当前互斥锁由哪个线程持有
3)记录重入次数
AbstractQueuedSynchronizer中的state属性记录重入的次数

Node节点

Node中有两个属性被用来标志当前的锁是共享锁还是独占锁,SHAREED标志当前线程抢的锁是共享锁,而EXCLUSIVE标志的是当前线程抢的锁是独占锁。

2.2 AbstractQueuedSynchronizer提供的功能

AQS实际上是抽象出锁的实现,提供了对资源占用、释放,线程的等待、唤醒等等接口和具体实现。
可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)

acquire、acquireShared:定义了资源争用的逻辑,如果没拿到,则等待。
tryAcquire、tryAcquireShared:实际执行占用资源的操作,如何判定由具体的AQS使用者去实现。
release、releaseShared:定义释放资源的逻辑,释放之后,通知后续节点进行争抢。
tryRelease、tryReleaseShared:实际执行资源释放的操作,由具体的AQS使用者去实现。

3 使用自定义的AQS实现读写锁

state除了记录重入次数之外,在读写锁中还会记录当前有多少个读线程持有该锁。当有线程请求读锁时,state++。

AQS中tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared这四个方法是抽象方法,由子类去实现。
在这个自定义的AQS中实现了acquire、release、acquireShared、releaseShared这四个方法

1)acquire
作用:获取独占锁

userDefinedAQS#acquire

2)release
作用:释放独占锁
逻辑:(注意此处的拿出是peek,要从等待池中移除得获取到锁才能移除)

userDefinedAQS#release

3)acquireShared
作用:获取共享锁

userDefinedAQS#acquireShare

4)releaseSharead
作用:释放共享锁
逻辑:(注意此处的拿出是peek,要从等待池中移除得获取到锁才能移除)

userDefinedAQS#releaseShare

在读写锁中,
tryAcquire:判断state是否为0,为0则说明没有线程获取读锁,继续CAS操作AQS中的owner属性(设置为当前线程),操作成功即线程抢到了锁(由CAS机制保证同一时刻只有一个线程能成功设置owner属性)
tryRelease:CAS操作AQS中的owner属性(设置为null),操作成功即表示释放锁成功
tryAcquireShared:判断是否owner属性有值(有值说明,已经有线程获取了写锁了),若是当前线程获取了写锁,则能继续获取读锁,否则获取读锁失败,若owner属性为null,则直接CAS state属性+1,成功则获取到了读锁。
tryReleaseShared:直接CAS state属性-1,成功则释放了读锁。

3.1 自定义AQS代码实现

package szu.vander.aqs.demo;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

/**
 * @author : Vander
 * @date :   2019/12/9
 * @description : 自定义抽象队列同步器
 */
public abstract class UserDefinedAQS {
    // 同步资源状态
    protected volatile AtomicInteger state = new AtomicInteger(0);
    // 当前锁的拥有者
    protected volatile AtomicReference owner = new AtomicReference<>();
    // 线程安全的队列
    public volatile LinkedBlockingQueue waiters = new LinkedBlockingQueue<>();

    /**
     * 获取独占锁
     */
    public void acquire() {
        // 塞到等待锁的集合中
        waiters.offer(Thread.currentThread());
        while (!tryAcquire()) {// 用while是因为被唤醒了可能还是抢不到要继续挂起
            // 挂起这个线程
            LockSupport.park();
        }
        // 后续,等待其他线程释放锁,收到通知之后继续循环
        waiters.remove(Thread.currentThread());
    }

    /**
     * 释放锁
     */
    public void release() {
        // cas 修改 owner 拥有者
        if (tryRelease()) {
            Thread waiter = waiters.peek();// 拿到队头的线程,而不是出队
            LockSupport.unpark(waiter); // 唤醒线程,继续抢锁
        }
    }

    /**
     * 尝试获取独占资源
     * @return
     */
    public abstract boolean tryAcquire();

    /**
     * 尝试释放独占资源
     * @return
     */
    public abstract boolean tryRelease();

    /**
     * 共享资源获取
     */
    public void acquireShared() {
        // 塞到等待锁的集合中
        waiters.offer(Thread.currentThread());
        while (tryAcquireShared() < 0) {// 返回剩余资源的个数
            // 挂起这个线程
            LockSupport.park();
        }
        // 后续,等待其他线程释放锁,收到通知之后继续循环
        waiters.remove(Thread.currentThread());
    }

    /**
     * 共享资源的释放
     */
    public void releaseShared() {
        // cas 修改 owner 拥有者
        if (tryReleaseShared()) {
            Thread waiter = waiters.peek();// 拿到队头的线程
            LockSupport.unpark(waiter); // 唤醒线程,继续抢锁
        }
    }

    /**
     * 尝试获取共享资源
     * @return
     */
    public abstract int tryAcquireShared();

    /**
     * 尝试释放共享资源
     * @return
     */
    public abstract boolean tryReleaseShared();

}

3.2 自定义ReadWriteLock代码实现

package szu.vander.aqs.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author : Vander
 * @date :   2019/12/15
 * @description :
 */
public class UserDefinedReadWriteLock implements ReadWriteLock {

    private final UserDefinedReadWriteLock.ReadLock readerLock;

    private final UserDefinedReadWriteLock.WriteLock writerLock;

    private final ReadWriteSync sync;

    public UserDefinedReadWriteLock() {
        sync = new ReadWriteSync();
        readerLock = new UserDefinedReadWriteLock.ReadLock(this);
        writerLock = new UserDefinedReadWriteLock.WriteLock(this);
    }

    static class ReadWriteSync extends UserDefinedAQS {

        /**
         * 获取共享资源,即state++,尝试获取读锁的时候调用此方法,本质是设置state,让state+1
         * @return
         */
        @Override
        public int tryAcquireShared() {
            // 如果当前有线程占用了写锁,则不允许再加锁,除非是同一个线程
            if (owner.get() != null && !owner.get().equals(Thread.currentThread())) {
                return -1;
            }
            return state.incrementAndGet();
        }

        /**
         * 尝试获取读锁
         * @return
         */
        public boolean tryReadLock() {
            if(tryAcquireShared() > 0) {
                return true;
            }
            return false;
        }

        /**
         * 释放共享锁,即state--
         * @return
         */
        @Override
        public boolean tryReleaseShared() {
            return state.decrementAndGet() >= 0;
        }

        /**
         * 获取独占锁,尝试获取写锁时调用,本质是设置owner为当前线程
         * @return
         */
        @Override
        public boolean tryAcquire() {
            // 有读的时候,不能写
            if (state.get() != 0) {
                return false;
            } else {
                return owner.compareAndSet(null, Thread.currentThread());
            }
        }

        /**
         * 尝试获取写锁
         * @return
         */
        public boolean tryWriteLock() {
            return tryAcquire();
        }

        /**
         * 释放独占锁
         * @return
         */
        @Override
        public boolean tryRelease() {
            return owner.compareAndSet(Thread.currentThread(), null);
        }

    }

    @Override
    public Lock readLock() {
        return this.readerLock;
    }

    @Override
    public Lock writeLock() {
        return this.writerLock;
    }

    public static class ReadLock implements Lock{

        private ReadWriteSync readWriteSync;

        protected ReadLock(UserDefinedReadWriteLock readWriteLock) {
            readWriteSync = readWriteLock.sync;
        }

        @Override
        public boolean tryLock() {
            return readWriteSync.tryReadLock();
        }

        @Override
        public void lock() {
            readWriteSync.acquireShared();
        }

        @Override
        public void unlock() {
            readWriteSync.releaseShared();
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
    }

    public static class WriteLock implements Lock{

        private ReadWriteSync readWriteSync;

        protected WriteLock(UserDefinedReadWriteLock readWriteLock) {
            readWriteSync = readWriteLock.sync;
        }

        @Override
        public boolean tryLock() {
            return readWriteSync.tryWriteLock();
        }

        @Override
        public void lock() {
            readWriteSync.acquire();
        }

        @Override
        public void unlock() {
            readWriteSync.release();
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            throw new UnsupportedOperationException();
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
    }

}

测试类:

package szu.vander.test.aqs;

import org.junit.Test;
import szu.vander.aqs.demo.UserDefinedReadWriteLock;
import szu.vander.log.Logger;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author : Vander
 * @date :   2019/12/15
 * @description :
 */
public class UserDefinedReadWriteLockTest {

    private UserDefinedReadWriteLock readWriteLock = new UserDefinedReadWriteLock();

    private Map container = new HashMap<>();

    private Logger log = new Logger();

    @Test
    public void test() throws InterruptedException {
        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                readWriteLock.writeLock().lock();
                log.info("获取到写锁");
                container.put("key1", "value1");
                readWriteLock.writeLock().unlock();
                log.info("释放写锁");
            }
        });

        Thread readThread1 = new Thread(new RunnableImpl());
        Thread readThread2 = new Thread(new RunnableImpl());
        Thread readThread3 = new Thread(new RunnableImpl());

        readThread1.start();
        TimeUnit.MILLISECONDS.sleep(200);
        readThread2.start();
        TimeUnit.MILLISECONDS.sleep(200);
        writeThread.start();
        TimeUnit.MILLISECONDS.sleep(200);
        // 等写锁获取到了之后,写完数据后再次获取读锁
        TimeUnit.SECONDS.sleep(5);
        readThread3.start();
        // 让程序运行完
        TimeUnit.SECONDS.sleep(10);
    }

    private class RunnableImpl implements Runnable {
        @Override
        public void run() {
            readWriteLock.readLock().lock();
            log.info("获取到读锁");
            log.info("读取Container中的内容,key1=" + container.get("key1"));
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            readWriteLock.readLock().unlock();
            log.info("释放读锁");
        }
    }

}

运行结果:

虽然写锁线程很早就启动了,但是由于读锁没有释放,所以写锁一直没有获取到直到读锁释放,写锁获取到了之后,其它线程就没办法获取读锁了,直到写锁的线程将写锁释放。

你可能感兴趣的:(11-2. 源码剖析AQS(AbstractQueuedSynchronizer)-抽象队列同步器)