AQS stands AbstractQueuedSynchronizer
, it is implemented JCU package almost all about locks, the cornerstone of important components of multi-threaded and thread synchronization, etc., the core idea is volatile int state
this property with Unsafe
the tools to achieve the current state of the lock to be modified.
1, AQS principles outlined
AQS interior of CLH maintains a FIFO queue, the queue of a basic structure as follows.
In the queue, each node represents a thread, but the thread is not only the head node will be thread-safe process.1.1, Node node
AQS used to represent each node of Node CLH queue, the source follows:
static final class Node {
//表示共享模式(共享锁)
static final Node SHARED = new Node();
//表示独占模式(独占锁)
static final Node EXCLUSIVE = null;
//表示线程已取消
static final int CANCELLED = 1;
//表示当前结点的后继节点需要被唤醒
static final int SIGNAL = -1;
//线程(处在Condition休眠状态)在等待Condition唤醒
static final int CONDITION = -2;
//表示锁的下一次获取可以无条件传播,在共享模式头结点有可能处于这种状态
static final int PROPAGATE = -3;
//线程等待状态
volatile int waitStatus;
//当前节点的前一个节点
volatile Node prev;
//当前节点的下一个节点
volatile Node next;
//当前节点所代表的的线程
volatile Thread thread;
//可以理解为当前是独占模式还是共享模式
Node nextWaiter;
//如果节点在共享模式下等待,则返回true。
final boolean isShared() {
return nextWaiter == SHARED;
}
//获取前一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
...
}
复制代码
1.2, into the team
If the current thread acquired the lock fails by CAS, AQS the thread will wait state and other information packaged into a node Node, and added to the tail of the queue synchronization, while the current thread is suspended.
public final void acquire(int arg) {
//tryAcquire是子类重写的方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//将线程及状态信息打包成一个节点
private Node addWaiter(Node mode) {
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;
}
//通过轮询方式将节点插入队列尾部
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//挂起当前线程
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;
}
//通过LockSupport来挂起当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
The overall flow diagram of the.
1.3 out team
When the lock is released, perform the operations team and wake successor node.
public final boolean release(int arg) {
//tryRelease是子类实现的方式
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//线程唤醒及出队操作
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
The overall flow diagram of the.
1.4 synchronous state management
Perhaps some doubts about the figure of the synchronizer. What in the end is it? Actually very simple, it is to give first and last nodes plus volatile
synchronous domain, as follows.
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
复制代码
AQS above three variables is a very important three variables, the first two variables well understood, the following terms about the state
variable, which is a counter in the exclusive lock case, after acquiring the lock, as will the value of state 1, the lock release is set to 0 (which locks belonging to non-reentrant lock); in the case of a shared lock, each thread to acquire the lock, it will state ++, when the lock is released state--; lock in reentrant case (acquired locks are the same lock), will each acquire a lock state ++, when the lock is released state--.
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
//使用CAS
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码
2, custom exclusive locks and shared locks
By the one to explain, we must have a certain understanding of AQS, following on to achieve an exclusive lock and a shared lock by AQS.
AQS is very strong, you only need to rewrite tryAcquire
, tryRelease
these two methods can achieve an exclusive lock. code show as below:
public class SingleLock implements Lock {
//自定义的独占锁
static class Sync extends AbstractQueuedSynchronizer {
//独占锁
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//独占锁
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//判断是是否是独占锁。
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
Condition newCondition() {
return new ConditionObject();
}
}
private Sync sync;
public SingleLock() {
sync = new Sync();
}
//加锁
@Override
public void lock() {
sync.acquire(1);
}
//获取可中断锁
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//获取锁,可能失败
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
//在time时间内不能获取锁则失败
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
//释放锁
@Override
public void unlock() {
sync.release(1);
}
//Condition来实现阻塞唤醒机制
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
复制代码
Very simple code on the realization of an exclusive lock, SingleLock
has ReentrantLock
most of the functions and usage of the same. Is not it simple...
JUC Pack for latching (a CountDownLatch) and semaphore (Semaphore) is to achieve the typical shared lock. Shared lock is also very simple, needs to be rewritten tryAcquireShared
, tryReleaseShared
these two methods. Here's to achieve a shared lock. code show as below:
public class ShareLock implements Lock {
static class Sync extends AbstractQueuedSynchronizer {
private int count;
Sync(int count) {
this.count = count;
}
@Override
protected int tryAcquireShared(int arg) {
for (; ; ) {
int current = getState();
int newCount = current - arg;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int arg) {
for (; ; ) {
int current = getState();
int newCount = current + arg;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
Condition newCondition() {
return new ConditionObject();
}
}
private int count;
private Sync sync;
public ShareLock(int count) {
this.count = count;
sync = new Sync(count);
}
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquireShared(1) >= 0;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.releaseShared(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
复制代码
ShareLock
Allow count
threads simultaneously acquire the lock, it's also very simple to achieve it. With these two examples above, we can follow their own needs to achieve different locks, but JUC package provides classes can basically meet most needs.
3, the lock reentrancy
SingleLock
Is an exclusive lock ourselves to achieve, but if you use it in a recursive, it will deadlock. Because SingleLock
not have reentrancy. So how to achieve reentrancy Nigeria? Look ReentrantLock
to achieve.
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//可重入性的实现,如果当前线程已拿到锁
else if (current == getExclusiveOwnerThread()) {
//状态加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//重新设置状态
setState(nextc);
return true;
}
return false;
}
复制代码
Can be found reentrancy implementation is quite simple, first determines whether the current thread is not already get the lock, the lock will have been given if the state value is incremented. Reentrancy This is very important, otherwise it will cause unnecessary deadlock, Synchronize
also have reentrancy.
4, fairness lock
SingleLock
Belonging to a non-arm lock, then how to achieve fair locks Nepal? In fact, this is more simple, just add a judgment can be. Look ReentrantLock
to achieve a fair lock.
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//如果当前线程之前还有节点则hasQueuedPredecessors返回true,就不会去竞争锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
复制代码
hasQueuedPredecessors
The key is to determine the fairness of the lock, it returns true if there are queued until a thread in the current thread, the current thread at this time would not have to compete lock. Thus ensuring the fairness of the lock.
5, thundering herd effect
In use wait/notify/notifyAll
, the thread is awakened use notifyAll
to wake up the thread, because notify
not wake specified thread, which may lead to a deadlock. But using notifyAll
also has a problem that when a large number of threads to acquire the lock will produce shock group effect , a large number of sharp increase in competition will inevitably lead to waste of resources and, therefore, after all, can only have a successful competition thread, other threads or to the old honest real wait to go back. The thundering herd effect problem of AQS FIFO queue waiting to resolve competition concerns in the lock offers a solution: Keep a FIFO queue, the queue of each node only interested in their previous node status, and only wake up thread wakes team head waiting thread .
[References]
AQS-depth study java synchronizer
On Java concurrent programming series (nine) - AQS structure and principle analysis
Pa Pa ReentrantLock as well as a realization of the principle of AQS