并发之AQS(AbstractQueuedSynchronizer)抽象的队列式的同步器,ReentrantLock/Semaphore/CountDownLatch等中的同步类实现都依赖于它。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
- isHeldExclusively():该线程是否正在独占资源。
AQS实现好了同步器的框架,具体是独占式还是共享式,只需要根据情况实现上面的不同方法即可。
AQS核心是维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
CountDownLatch/Semaphore在初始化的时候均需要设置资源数量state,也即有多少资源可以被共用,一般大于1个才能达到并发效果。
ReentrantLock,每次调用lock时会把资源state尝试设置成1,如果失败则调用tryAcquire(1)尝试将state+1,(其他线程调用tryAcquire会失败,如果还是自己这个线程调用才能成功使state+1, 这也就时可重入锁的意思),每次调用unlock会尝试调用tryRelease(1)释放资源,直到state=0为止。
————————————————————————————————
AQS方法解析
acquire(int)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.尝试获取资源。
2.addWaiter将线程添加到等待队列尾部,标记为独占模式。
3. acquireQueued通过循环调用tryAcquire尝试获取资源,如果获取失败,则调用park方法使线程处于阻塞状态, 如果获取成功则返回。
4 . 获取到资源后,执行selfInterrupt() 调用Thread.currentThread().interrupt();
tryAcquire(int)
尝试获取资源,AQS中并没有实现. 需要子类实现,子类可以根据需要实现。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
release(int)
public final boolean release(int arg) {
if (!tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus !=0)
unparkSuccessor(h);
return true;
}
return false;
}
其中waitStatus :
SIGNAL : 1 表示线程阻塞中,需要unpark;
CANCELLED:-1 线程被取消,表示由于超时或者interrupt,非阻塞状态。
CONDITION :-2 表示当前节点在等待condition,也就是在condition队列中。
PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
值为0,表示当前节点在sync队列中,等待着获取锁。
UnparkSuccessor(Node)方法:从尾部向前查找该节点之后并且最靠前的需要唤醒的资源,并唤醒(调用LockSupport.unpark)。
——————————————————————————————————————————————
acquireShared(int)
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
通过tryAcquireShared()尝试获取锁,如果获取失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。
doAcquireShared(int) :尝试获取共享资源,直到获取到资源。
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED);//共享模式 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//获取前一个节点 if (p == head) {//如果是头节点 尝试获取资源 int r = tryAcquireShared(arg); if (r >= 0) {//资源获取成功,r为剩余资源数量 setHeadAndPropagate(node, r);//如果还有剩余资源,可以继续唤醒其他线程 p.next = null; // help GC if (interrupted)//如果中断过 selfInterrupt();//中断 failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
shouldParkAfterFailedAcquire
检查前一个节点状态,是否为SIGNAL状态,如果是返回true,如果不是,则设置成SIGNAL状态。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
setHeadAndPropagate(Node,int)
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node);//设置成头节点 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); //尝试释放剩余共享资源 } }
releaseShared(int) 释放共享资源
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
doReleaseShared(int) 释放共享资源
private void doReleaseShared() {
for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) {//需要被unpark唤醒 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//设为释放状态,失败,继续循环。 continue; // loop to recheck cases unparkSuccessor(h);// 通过调用 LockSupport.unpark,唤醒资源 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
——————————————————————————————————————————————————————
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。