Concurrent Notes Portal:
1.0 Concurrent Programming-Mind Map
2.0 Concurrent Programming-Thread Safety Fundamentals
3.0 Concurrent Programming-Basic Building Module
4.0 Concurrent Programming-Task Execution-Future
5.0 Concurrent Programming-Multi-threaded Performance and Scalability
6.0 Concurrent Programming- Explicit lock and synchronized
7.0 concurrent programming-AbstractQueuedSynchronizer
8.0 concurrent programming-atomic variables and non-blocking synchronization mechanism
In
ReentrantLock
andSemaphore
there is much in common between the two interfaces. Both of these classes can be used as a "valve" that allows only a certain number of threads through, and when the thread reaches the valve, by (the calllock
oracquire
successfully returns), you can also wait for (the calllock
oracquire
when Blocking), or cancel ( return "false" when callingtryLock
ortryAcquire
, which means that the lock is unavailable within the specified time or cannot get permission). Moreover, these two interfaces both support interruptible, non-interruptible, and time-limited acquisition operations, and both support waiting threads to perform fair or unfair queue operations.
In fact, they all use a common base class, namely
AbstractQueuedSynchronizer
(AQS
), which is also the base class of many other synchronous classes.AQS
It is a framework for building locks and synchronizers. Many synchronizers can be constructed easily and efficiently through AQS. Not onlyReentrantLock
andSemaphore
is based on theAQS
building, but also includeCountDownLatch
,ReentrantReadWriteLock
,SynchronousQueue
andFutureTask
.
Acquisition and release in AQS
In the synchronizer class based on AQS, the most basic operations include various forms of lock acquisition and release operations. And the get operation is a state-dependent operation and usually blocks.
The following pseudo code shows the simple logic of AQS acquisition and release. (Douge Lea's source code is too delicate, so you have to taste it slowly)
boolean acquire() throws InterruptedException{
while (当前状态不允许获取操作){
if (需要阻塞获取请求){
如果当前线程不再队列中,则将其插入队列
阻塞当前线程
}else {
返回失败
}
}
更新同步器的状态
如果线程位于队列中,则将其移除队列
返回成功
}
void release(){
更新同步器的状态
if(新的状态允许某个阻塞的线程获取成功){
解除队列中一个或多个线程的阻塞状态
}
}
A get operation consists of two parts:
- First, the synchronizer judges whether the current state allows the acquisition operation, and if so, the thread is allowed to execute, otherwise the acquisition operation will block or fail. This judgment is based on the semantics of the synchronizer. For example: for a lock, if it is not held by a thread, it can be successfully acquired; for a lock, if it is in the end state, it can also be successfully acquired.
- Secondly, it is to update the state of the synchronizer. A thread that obtains the synchronizer may affect whether other threads can obtain the synchronizer. For example, when acquiring a lock, lock state will "not prepared to hold" to "has been held", and from
Semaphore
post-acquisition license, will reduce the number of licenses 1. However, when a thread acquires a lock, it does not affect whether other threads can acquire it.
Depending on the nature of the synchronizer, the implementation methods are different:
- Exclusive operation (for example:
ReentrantLock
): If a synchronizer get exclusive support operations, the need to achieveAQS
thetryAcquire
,tryRelease
,tryHeldExeclusively
and other methods. - Non-exclusive operation (for example:
Semphore
,CountDownLatch
): support for synchronization acquisition is shared, it should be realizedtryAcquireShared
,tryReleaseShared
other methods
AQS
In the acquire
, acquireShared
, release
, releaseShared
and other methods will call these methods subclasses prefixed try
version to determine whether an operation can be performed.
A simple lock
OneShotLatch
Contains two public methods: await
and signal
, which correspond to the acquisition and release operations respectively. At first, the latch is closed, any calling await
thread will be blocked until the blocking and open. When by calling signal
the open latch, all the threads of milk waiting to be released, and then reaches the locking thread are also allowed to be executed.
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class OneShotLatch {
private final Sync sync = new Sync();
public void signal() {
sync.releaseShared(0);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(0);
}
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected int tryAcquireShared(int arg) {
int state = getState();
//如果闭锁是开的(state==1),那么这个操作讲成功,否则失败
System.out.println("state = " + state);
return getState() == 1 ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
setState(1);//打开闭锁
return true;//其他线程可以获取该闭锁
}
}
public static void main(String[] args) {
OneShotLatch osl = new OneShotLatch();
new Thread(() -> {
System.out.println("we are in main 01 thread, and start osl await");
try {
osl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 01 thread osl await finished");
}).start();
new Thread(() -> {
System.out.println("we are in main 02 thread, and start osl await");
try {
osl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 02 thread osl await finished");
}).start();
new Thread(() -> {
System.out.println("we are in main 03 thread, and first sleep 5s");
try {
Thread.sleep(5000);
System.out.println("we are in main 03 thread, and start osl await");
osl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 03 thread osl await finished");
}).start();
new Thread(() -> {
System.out.println("we are in work thread,and we start waiting");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("work finish,now we signal main thread");
osl.signal();
}).start();
}
}
AQS in java.util.concurrent
And contracting can be blocked in a number of categories, e.g.
ReentrantLock
,Semaphore
,CountDownLatch
,ReentrantReadWriteLock
,SynchronousQueue
andFutureTask
the like, are basedAQS
construction.
ReentrantLock
ReentrantLock
Only supports exclusive access to operate, so it implementstryAcquire
,tryRelease
andisHeldExclusively
methods.
ReentrantLock
The synchronization state isstate
used to save the number of lock acquisition operations.- It maintains a
owner
variable to hold the current thread, but was reconstructed increased on 1.6AbstractOwnableSynchronizer
withexclusiveOwnerThread
to save the current thread. This variable is only modified when the current thread has just acquired the lock or is about to release the lock.- In
tryRelease
the inspectionowner
field, so as to ensure the implementation of the current threadunlock
before the operation has already acquired a lock - In
tryAcquire
use in theowner
field determines whether the acquired operation is re-entrant or competition
- In
Unfair lock version (default)
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
When a thread tries to acquire a lock, it tryAcquire
will first check the state of the lock:
- Not hold: The
compareAndSetState(0, acquires)
update lock attempt to represent atomic operation has been held. - Already holds: determine whether the current site is the lock owner, is: count is incremented (it
ReentrantLock
is reentrant lock); not: Get the operation failed.
Semaphore
versus CountDownLatch
Semaphore
AndCountDownLatch
is part of a shared support obtained synchronizer, they realizedtryAcquireShared
andtryReleaseShared
methods
Semaphore
The state
available license number is currently used for storage.
Take Semaphore
the implementation of unfair locks as an example:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
//这个代码很精髓啊
//remaining < 0 :如果没有足够的许可,退出循环
//compareAndSetState设置成功,退出循环;设置失败,重新尝试
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
tryAcquireShared
First, the number of remaining licenses is calculated.
- If the number is insufficient, a value will be returned to indicate that the get operation failed.
- If there are remaining licenses, the license count will
compareAndSetState
be reduced atomically. If the operation is successful (meaning that it has not been modified since the last read), then a value is returned to indicate that the operation was successful.
CountDownLatch
Use AQS
way with Semaphore
very similar: synchronization status state
to save the current count value of the
await()
call relationship:
graph LR
await-->acquireSharedInterruptibly
acquireSharedInterruptibly --> tryReleaseShared
await
Call acquire
, when the counter is 0, it acquire
will return immediately, otherwise the execution will doAcquireSharedInterruptibly
enter the block.
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
countDown()
Calling relationship:
graph LR
countDown-->releaseShared
releaseShared-->tryReleaseShared
countDown()
Call tryReleaseShared
to complete the count is decremented when the count value is 0, execution doReleaseShared
unblock all waiting threads.
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
FutureTask
The new version of the old man is not used directly
AQS
,sun.misc.Unsafe UNSAFE
and is realized through operation.
ReentrantReadWriteLock
ReadWriteLock
It indicates the presence of two interfaces locks: locks read and write locks, but basedAQS
implementationReentrantReadWriteLock
, a singleAQS
subclass manage read lock and a write lock.
ReentrantReadWriteLock
Two 16-bit states are used to represent the count of write lock and read lock respectively. Operations on read locks use shared acquisition and release methods; operations on write locks use exclusive acquisition and release methods.
AQS
A queue of waiting threads is maintained internally, which records whether a thread has exclusive ( Node.EXCLUSIVE
) or shared ( Node.SHARE
) access.
summary
AQS
Source really complicated, just under Benpian superficial and record contract inAQS
usage, the next fight for the next biteAQS
realization of the principle of
Doug Lea
The old man raised the banner of java concurrency alone, really.
The following is a good AQS source code analysis on the Internet, record it