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.