AQS 有 3 要素:state,队列,exclusiveOwnerThread

ReentrantLock 和 CountDownLatch 内部都有一个 Sync 变量,ReentrantLock 的分为公平锁和非公平锁,默认使用非公平锁,允许线程抢占锁。

对于 ReentrantLock,state 初始值为 0,线程 CAS 修改 state 值,成功把 0 改成 1,则获取了锁,置 exclusiveOwnerThread。

private volatile int state;

如果有其他线程获取 "互斥" 锁,发现 state != 0,则阻塞当前线程,把互斥节点放入队列中。

当持有锁的线程释放锁时,state 值减一,如果为 0,则唤醒一个队列中的互斥节点。

 

对于 CountDownLatch 

state 变量有一个初始值,线程调用 await,尝试索取 "共享" 锁,如果 state 值为 0,马上返回,一般情况 state > 0,所以线程会被阻塞,把共享节点放入队列中。

CountDownLatch(int state)

当其他线程 countDown 时,对 state 减一,当然这个操作肯定是 CAS 的,如果 state 为 0,则唤醒队列中的 "共享" 节点。

 

AQS 类有几个抽象方法,等待它的子类实现,比如 ReentrantLock 和 CountDownLatch 的 Sync 变量

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

 

 

Semaphore(int state)

对 state 设置初始值,acquire 对 state 减一,release 对 state 加一,当 state 变为零,acquire 阻塞放入队列。

关于 Condition

ReentrantLock.newCondition

AQS 里面有个队列,节点是等待锁的线程,ConditionObject 里面有个队列,节点是等待条件的线程,为描述方便,前后两个队列称为 A 和 B

线程获取锁失败,加入队列 A 的尾部

lock.lock()
doBiz()
lock.unlock()

private Node addWaiter(Node mode) {
    // 创建 Node
    Node node = new Node(Thread.currentThread(), mode);
    // 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)) {
            pred.next = node;
            return node;
        }
    }
    // 也是加入队列尾部,后备
    enq(node);
    return node;
}

线程获得锁之后,调用 await,则把当前线程加入到队列 B 尾部,当有线程调用 signal 时,把队列 B 中的元素取出,放入队列 A

ReentrantLock.newCondition()
lock.lock
con.await/con.signal
lock.unlock


把节点从队列 B 中取出,放入队列 A
// AbstractQueuedSynchronizer.ConditionObject#doSignal
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

// AbstractQueuedSynchronizer#transferForSignal
final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

 

CountDownLatch

线程 1 中调用 latch.await,线程 2 中调用 latch.await,则两个线程都阻塞,队列的节点是 SHARED 模式,当 latch.countDown 到 0 时,线程 1 和 线程 2 都会被唤醒。

线程 1 先被唤醒,然后线程 1 开始执行阻塞点之后的代码, 于是线程 1 唤醒线程 2。

 

共享和互斥

线程获取锁失败,则放入队列尾部,阻塞当前线程,对于共享和互斥锁都是一样的;
当持有锁的线程释放锁时,如果 state 值为 0,则唤醒队列中的节点,这对于共享和互斥锁也是一样的;

那么不一样的地方在哪呢?

当线程被唤醒时,则尝试去获取锁,如果获取锁成功,对于共享锁,则继续唤醒下一个等待的节点,接力下去,对于互斥锁,成功获取锁后,直接返回

共享锁

// java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 当线程醒来时,尝试获取共享锁
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 获取共享锁成功,则唤醒下一个节点
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 阻塞当前线程
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

互斥锁

// java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireInterruptibly
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            // 当线程醒来时,尝试获取互斥锁
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 获取互斥锁成功就返回,不会再接力下去了
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 阻塞当前线程
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

通常都是使用非公平锁,非公平锁和公平锁的区别是,允许当前线程抢占锁,即使队列中有线程在等待。

非公平锁和公平锁的获取锁的逻辑不同:
公平锁,当 state 为 0 时,先去检查队列中是否有等待节点,如果有,则放弃锁,进入等待队列
非公平锁,当 state 为 0 时,立刻调用 cas 去修改 state 值,抢占锁

posted on 2020-02-11 17:16  偶尔发呆  阅读(150)  评论(0)    收藏  举报