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 值,抢占锁
浙公网安备 33010602011771号