ReentrantLock
Already is a Java. Util. Concurrent. The locks bag under a reentrant default is fair Lock, already class is a Lock interface using very frequent implementation class, class structure is as follows:
As mentioned earlier, the JMM model requires visibility, atomicity, and order. There are many solutions to atomicity, such as synchronized synchronization or synchronized code blocks, or AtomicInteger atom-wrapped classes. Synchronized locking is known to be the most cumbersome solution. Here we use ReentrantLock to implement atomicity, with the following code:
class MyData {
int num = 0;
Lock lock = new ReentrantLock();
public void add(a) {
try {
lock.lock();
num++;
} finally{ lock.unlock(); }}}public class ReentrantLockDemo {
private static Logger log = LoggerFactory.getLogger(ReentrantLockDemo.class);
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myData.add();
}
}, String.valueOf(i)).start();
}
// Until all threads are finished executing
while (Thread.activeCount() > 1) {
Thread.yield();
}
log.info("Result: {}", myData.num); }}Copy the code
1.1. Fairness of ReentrantLock
ReentrantLock provides a constructor with arguments to let the consumer decide whether to use a fair lock.
We can know through the source code, no parameter is the default is not fair lock, pass true to indicate fair lock, pass false to indicate unfair lock, the source code is as follows
// Empty parameters default to an unfair lock
public ReentrantLock(a) {
sync = new NonfairSync();
}
Copy the code
// The root argument determines the fairness of the lock
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
1.2. Unfair ReentrantLock
1.2.1 Adding a Lock using ReentrantLock
// Create an unfair lock
Lock lock = new ReentrantLock();
try {
/ / lock
lock.lock();
} finally {
/ / releases the lock
lock.unlock();
}
Copy the code
1.2.2. The Lock method of ReentrantLock is performed
public void lock(a) {
sync.lock();
}
Copy the code
1.2.3. The lock method of the NonfairSync class is called
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(a) {
// Set the State value from 0 to 1 in the AQS queue via CAS
if (compareAndSetState(0.1))
// If the lock is acquired successfully, the current thread is marked as the thread holding the lock, and then returns directly
setExclusiveOwnerThread(Thread.currentThread());
else
// Execute this method if the lock fails to be acquired
acquire(1); }}Copy the code
1.2.4. Call cAS method of AQS to obtain lock
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
// The unsafe class manipulates memory data directly through native methods
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Copy the code
1.2.5. Failed to acquire the lock. Run the acquire method
public final void acquire(int arg) {
if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code
1.2.6. TryAcquire attempted to acquire the lock
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
Copy the code
The tryAcquire method is the default hook method of AQS, and there are different implementations of different classes. NonfairSync is implemented as follows:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
Copy the code
final boolean nonfairTryAcquire(int acquires) {
Acquires is passed as 1 to obtain the current thread
final Thread current = Thread.currentThread();
// Gets the value of the state variable, which is the number of times the current lock has been reentered
int c = getState();
// State is 0, indicating that the current lock is not held by any thread
if (c == 0) {
// Set the State value from 0 to 1 in the AQS queue via CAS
if (compareAndSetState(0, acquires)) {
// If the lock is acquired successfully, the current thread is marked as the thread holding the lock, and then returns directly
setExclusiveOwnerThread(current);
// Attempts to obtain the lock succeeded
return true; }}// // The current thread is the thread that holds the lock, indicating that the lock is re-entrant
else if (current == getExclusiveOwnerThread()) {
// // calculates the value of the state variable to update
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// // Update the state value asynchronously
setState(nextc);
// The lock is successfully obtained
return true;
}
// Failed to obtain the lock
return false;
}
Copy the code
1.2.7. Thread is added to the synchronization queue after lock acquisition failure
private Node addWaiter(Node mode) {
// Create a new node with the mode parameter null
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// tail is the tail pointer to the element at the end of the queue. The new node's head pointer points to the tail pointer of the queue
Node pred = tail;
// // the queue is not empty
if(pred ! =null) {
// Change the head pointer of the new node to the tail pointer of the queue
node.prev = pred;
// With the CAS algorithm, the new node points to the tail pointer if the queue in memory is the same as the previous one
if (compareAndSetTail(pred, node)) {
pred.next = node;
returnnode; }}// Safely join the synchronization queue
enq(node);
return node;
}
Copy the code
1.2.8. Adding a thread to a synchronization queue
private Node enq(final Node node) {
for (;;) {
// the t node points to the last node in the current queue
Node t = tail;
// The queue is empty
if (t == null) { // Must initialize
// Construct a new node through CAS
if (compareAndSetHead(new Node()))
// The tail pointer points to the new node
tail = head;
} else {
// When the queue is not empty, point the node's head pointer to the queue's tail pointer
node.prev = t;
// With the CAS algorithm, the new node points to the tail pointer if the queue in memory is the same as the previous one
if (compareAndSetTail(t, node)) {
t.next = node;
returnt; }}}}Copy the code
When the queue is empty, use CAS to update the header node.
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
Copy the code
Note: The update succeeds only when the original value in the queue is NULL.
When the queue is not empty, CAS updates the tail node source code as follows:
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
Copy the code
Note: The tial pointer is updated in CAS mode. The update succeeds only when the original value is t
1.2.9. After the thread enters the queue
final boolean acquireQueued(final Node node, int arg) {
// Arg is 1 and the node is the thread node for which the lock was acquired
boolean failed = true;
try {
boolean interrupted = false;
// The thread enters an infinite loop. Normally, the thread can't exit the loop until it acquires the lock
for (;;) {
// Get the precursor node of the current node thread
final Node p = node.predecessor();
// When the queue head node is obtained or the attempt to obtain the lock succeeds
if (p == head && tryAcquire(arg)) {
// Set the node of the current thread to the head node
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted; // The only exit from the loop
}
if (shouldParkAfterFailedAcquire(p, node) && // Determine whether to block the current thread
parkAndCheckInterrupt()) // Block the current thread
interrupted = true; }}finally {
if(failed) cancelAcquire(node); }}Copy the code
1.3.ReentrantLock summary
1.4.ReentrantLock Unfair unlocking
lock.unlock();
Copy the code
1.4.1. Release the lock
public void unlock(a) {
sync.release(1);
}
Copy the code
public final boolean release(int arg) {
// Release the lock (state-1), and return true if the lock can be acquired by another thread (state=0)
if (tryRelease(arg)) {
// Get the head node of the queue
Node h = head;
// The current queue is not empty and the header state is not initialized (0)
if(h ! =null&& h.waitStatus ! =0)
// Wake up the blocked thread in the synchronization queue
unparkSuccessor(h);
return true;
}
return false;
}
Copy the code
1.4.2. Attempt to release the lock
protected final boolean tryRelease(int releases) {
// Calculate the state value to be updated
int c = getState() - releases;
if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
boolean free = false;
// The state value to be updated is 0, indicating that the thread holding the lock is not reentrant. Once the lock is released, other threads can acquire it
if (c == 0) {
free = true;
// Clear the thread holding flag for the lock
setExclusiveOwnerThread(null);
}
// Update the state value
setState(c);
return free;
}
Copy the code
We can see a very important abstract class AbstractQueuedSynchronizer, relevant AQS later in screwing, AQS is the basis of the synchronous components, so to speak.