ReentranLock class implements
Our common lock is ReentranLock, which can support most of our scenarios with simple support for reentrant.
Lock lock = new ReentrantLock();
/ / lock
lock.lock();
/ / unlock
lock.unlock();
Copy the code
There are two internal implementations of ReentranLock,fairLocks andNot fairThe lock. Looking at the class diagram, the internal class sync has two implementations, our fair and unfair locking. Sync integrates AQS and overrides some of the methods.
Look at the ReentranLock class
// The main synchronization function is implemented
private final Sync sync;
// The default constructor uses an unfair lock implementation
public ReentrantLock(a) {
sync = new NonfairSync();
}
// Use a variable to control whether the lock is fair
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// As you can see, our main implementations call sync's methods
public void lock(a) {
sync.lock();
}
public void unlock(a) {
sync.release(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
Copy the code
Looking at the ReentranLock source code, the main synchronization logic is in Sync. Let’s analyze the implementation logic of the two locks one by one.
Let’s start with a brief introduction to the internal structure of AQS. The general idea is that when we have multiple threads that want to acquire the lock, the threads that don’t acquire the lock need to be queued up for a first-in, first-out process. Synchronizers have references to header and tail nodes. Node: Wrapper class for Thread that failed to acquire the lock, combined with Thread references, implemented as a FIFO bidirectional queue.The head of the list initialization is actually a virtual node, called the dummy Header, because it does not hold real threads like its successors, and this node is only delayed initialization to avoid performance waste, as I’ll mention again when I look at the code below. AbstractQueuedSynchronizer class is a template class, maintenance with a synchronous queue (two-way chain table), provide the synchronous queue some of the public methods of operation, JUC and contract awarding Ricky many common concurrency tool was realized in this class, Semaphore, CountDownLatch, etc.
Int The synchronization state. The initial value is 0 */
private volatile int state;
Copy the code
AbstractQueuedSynchronizer maintains a state variable, to represent the state of synchronizer, the state could be called the soul of AQS, based on many of AQS realize JUC tool, is achieved through the operation state, the state of 0 indicates no any thread holds a lock; A state of 1 means that a thread has acquired the lock once, and a state of n(n > 1) means that the thread has acquired the lock n times, which is used to express the concept of “reentrant locks”.
Locking logic of unfair lock
Lock () method logic: multiple threads call the lock() method, if the current state is 0, no thread currently holds the lock, then only one thread will CAS the lock, and set this thread as the exclusive lock thread. Other threads would then call the acquire method to compete for the lock (and then all would be spun or suspended in the synchronization queue). When another thread A comes in and wants to acquire the lock, and one of the previous threads just releases the lock, A will acquire the lock before all the other threads in the synchronization queue. That is to say, all threads in the synchronization queue that have not been canceled are absolutely guaranteed to acquire the lock serially, while other new threads may acquire the lock first, which is also the idea of unfair locking.
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// *** *** *** *** *** *** *** *** *** **
final void lock(a) {
if (compareAndSetState(0.1))
setExclusiveOwnerThread(Thread.currentThread());
else
// If queue-jumping fails, go through the normal process and apply for a resource
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
returnnonfairTryAcquire(acquires); }}Copy the code
If the tryAcquire method fails to acquire the lock, return false to proceed to the next step. AddWaiter wraps the current thread to the end of the wait queue as a Node.
public final void acquire(int arg) {
// tryAcquire has been overwritten in NonfairSync
// 1. Second attempt to jump queue and lock tryAcquire
2.AddWaiter (Node.exclusive) Node.EXCLUSIVE indicates EXCLUSIVE modeif (! tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.The tryAcquire method failed to retrieve the lock on its second attemptfalse
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) {
// Second attempt at queue jumping
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true; }}// Check whether the current thread is equal to the thread holding the lock
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// Failed to hold the lock, return
return false;
}
2.The addWaiter method basically encapsulates the current thread as a Node and places the end of the queue1.Node encapsulates the current thread2.Tail The tail node is notnull, points Node.prev to the tail, then CAS replaces the tail reference with itself, and returns3.If the tail is zeronullThe queue is not initialized4.CAS fails to replace the tail node. If other threads succeed in robbing TALI, enQ is invoked to force them to join the queueprivate Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Get the tail is not null
Node pred = tail;
if(pred ! =null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
1.Check whether the queue is empty. If it is empty, initialize the queue and join the queue2.Don't empty,forThis loop forces the CAS to join the queueprivate 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;
returnt; }}}}3.Failed flag indicates whether acquire succeeded and whether the interrupted flag has been suspended. Pay attention to thefor(; 😉 is the only condition to jump outif(p == head && tryAcquire(ARG)); We should see that this is the third time that a thread tries to acquire the lock quickly: even though the node has been queued, it tries to acquire the lock again through the P == head && tryAcquire(ARG) method before going to sleep. That is to say, only the thread nodes all effective front with a lock, the current node to have the opportunity to compete for the lock, if failed through shouldParkAfterFailedAcquire method determine whether should suspend the current node, waiting for a response. By observing the timing of each call to the tryAcquire method, we can see the author's optimization intent:final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// Check whether the front node is headHead will tryAcquire an attempt to jump the queue to acquire the lockfinal Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// If a certain condition is reached, the queue will be suspended
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; }}finally {
if(failed) cancelAcquire(node); }}Copy the code
The process of locking can be roughly divided into the above steps. It can be seen that the unfair lock will be queue-jumping and locking for more than two times. In the case that the lock has not been added, it will be serial waiting in the queue. Here’s a flow chart to help you understand
Locking logic of fair lock
The main code difference between fair and unfair locks is in the tryAcquire method.
static final class FairSync extends Sync {
final void lock(a) {
// No more queue-jumping
acquire(1);
}
/** * tryAcquire also lost the queue attempt lock code */
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/ / hasQueuedPredecessor hereThe current queue is empty or is itself a header in a synchronous queue. If the conditions are met, the CAS obtains the synchronization status and sets the current exclusive threadif(! 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; }}Copy the code
Reference: blog.csdn.net/lsgqjh/arti…