I understand AQS thoroughly. How about you?

Keywords: Java Multithreading

background

I wrote an article on understanding AQS in depth earlier

Thoroughly understand AQS

After a period of time, I found that I couldn't remember some places clearly, and what I had written before was not good, so I didn't feel that I understood them thoroughly, so I decided to look for information again and thoroughly understand what I knew before!

Look at the document

Let's first look at the source code. The AbstractQueuedSynchronizer class has 2335 lines of code + comments, which can be said to be very long. Let's first look at the first-hand information, that is, how the source code comments are written. Let's learn about this class through the annotation outline of AbstractQueuedSynchronizer class.

Class annotation

The original English text is not pasted here. It is relatively long. The translated content is directly:

This class provides a framework for implementing blocking locks and synchronizers (semaphores, events, etc.), which relies on first in first out (FIFO) waiting queues. It provides the implementation basis for most synchronizers that represent the state through a single atomic value of type int. The subclass must override the protected method that changes the synchronizer state and define the specific meaning of the state in obtaining or releasing subclass objects. Based on this, other methods in this class implement all queuing and blocking mechanisms. Subclasses can maintain other state fields, but in terms of synchronization, only int values updated atomically using methods getState, setState and compareAndSetState operations can be tracked.

Subclasses should be defined as non-public internal auxiliary classes to implement the synchronization properties of their external classes. The AbstractQueuedSynchronizer class does not implement any synchronization interfaces. Instead, it defines methods such as acquireinterruptibly, which can be appropriately called by specific locks and associated synchronizers to implement their public methods.

This class supports exclusive mode and shared mode to obtain synchronization status by default. When obtaining synchronization status in exclusive mode, other threads will not succeed in trying to obtain synchronization status. In shared mode, multiple threads generally (but not necessarily) succeed in obtaining synchronization status. This class will ignore the mechanism difference between the two modes, that is, when the thread successfully obtains the synchronization state in the shared mode, the next waiting thread (if any) must also determine whether it can also obtain the state.

Waiting threads in different modes share a FIFO queue.

Usually, subclasses implement only one pattern, but the two can work at the same time, such as ReadWriteLock. Only subclasses of one schema are supported, and there is no need to define methods for another schema.

AQS defines an internal class ConditionObject, which can be used as an instance of Condition for subclasses that support exclusive mode. In exclusive mode, method #isHeldExclusively is used to indicate whether the current thread is monopolizing subclass objects. With the return value of method #getState as the input parameter, calling method #release can completely release subclass objects. Use this value to call #acquire method, and finally return to the previous lock acquisition state. There is no other method in AbstractQueuedSynchronizer to create this Condition, so if this constraint cannot be met, do not use it. The behavior of AbstractQueuedSynchronizer.ConditionObject is of course based on the semantics of its implementation class.

This class provides inspection / instrumentation / monitoring methods for internal classes and similar methods for condition objects. These can be exported to classes that use AbstractQueuedSynchronizer as the synchronization mechanism. The serialization of this class only stores the maintenance state of atomic integers, so the thread queue obtained by deserializing the object is empty. If a subclass needs serialization, it needs to define readObject() to restore itself to a known initial state.

Usage:

If you use this class as the basis of the synchronizer, redefine the following methods using getState()/setState()/cas, which are used to check or modify the synchronization state:

  • tryAcquire

  • tryRelease

  • tryAcquireShared

  • tryReleaseShared

  • isHeldExclusively

These methods throw UnsupportedOperationException by default. These methods must ensure thread safety internally, and should usually be short and lockless. Defining these methods is the point of using this class. Other methods cannot be changed independently, so they are declared final.

You can also see the method inherited from AbstractOwnableSynchronizer, which is used to track the threads that monopolize the synchronizer. You are encouraged to use these methods. They enable monitoring and diagnostics to help users determine which threads hold locks.

Even if this class is based on the internal FIFO queue, it will not execute the default FIFO policy. The core of exclusive synchronization uses the following policies:

Acquire:
    while (!tryAcquire(arg)) {
          enqueue thread if it is not already queued;
          possibly block current thread;
       }
 
   Release:
       if (tryRelease(arg))
          unblock the first queued thread;

(the sharing mode is similar, but may involve cascaded signals.)

Because the check for acquiring locks is performed before queuing, a new thread may be inserted before other queued threads. Anyway, if you like, you can define tryAcquire() / tryacquishared() by calling one or more internal check methods Method to provide a fair FIFO acquisition order. In particular, most fair synchronizers let tryAcquire() return false when hasQueuedPredecessors() (which is specially designed for fair synchronizers) returns true. Of course, other methods are also possible.

For the default queue jumping policy (also known as green / Renewal / comfort avoidance), throughput and scalability are usually the highest. Although this does not guarantee fairness, it allows early queued threads to re compete before subsequent threads, and each re competing thread has a fair chance to beat the new thread. Although the acquisition behavior usually does not go on all the time, threads are accompanied by other plans before being blocked During the calculation process, they may call tryAcquire() multiple times. When the exclusive synchronizer is only held for a short time, it is very good for spin and does not have too much burden. You can enhance this through the previous call to obtain the method with "fast path". If the synchronizer may not be contested, it may only check hascontented() and hasQueuedThreads() in advance.

This class provides an effective and extensible basis for synchronization, in part because it focuses on synchronizers that depend on digital state, get / release parameters, and internal FIFO. When these cannot meet the needs, you can use atomic classes, your own java.util.Queue classes, and locksupplier to build lower level synchronizers.

Usage example:

This is a non reentrant mutex. 0 means open and 1 means locked. Although the non reentrant lock does not need to strictly record the thread of the local owner, this class does so to make it easier to monitor. It also supports conditions and discloses a detection method.

class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
        // Acquires the lock if state is zero
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Releases the lock by setting state to zero
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // Reports whether in locked state
        public boolean isLocked() {
            return getState() != 0;
        }

        public boolean isHeldExclusively() {
            // a data race, but safe due to out-of-thin-air guarantees
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // Provides a Condition
        public Condition newCondition() {
            return new ConditionObject();
        }

        // Deserializes properly
        private void readObject(ObjectInputStream s)
                throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isLocked();
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

This is a latch class similar to java.util.concurrent.CountDownLatch CountDownLatch, but it only needs a signal to trigger. Because the latch is non exclusive, it uses shared get and release methods.

class BooleanLatch {

    private static class Sync extends AbstractQueuedSynchronizer {
        boolean isSignalled() {
            return getState() != 0;
        }

        protected int tryAcquireShared(int ignore) {
            return isSignalled() ? 1 : -1;
        }

        protected boolean tryReleaseShared(int ignore) {
            setState(1);
            return true;
        }
    }

    private final Sync sync = new Sync();

    public boolean isSignalled() {
        return sync.isSignalled();
    }

    public void signal() {
        sync.releaseShared(1);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
}

If it's hard to understand the above translation, put it there. After reading the following, go back to the top and see it again.

Look at the design

AQS is essentially a FIFO two-way queue. Threads are packaged in the form of nodes and wait in the queue to obtain resources based on the spin mechanism (resources here can be simply understood as object locks)

Taking a look at this class, you can see that there are two internal classes, and the rest is a pile of member variables and member methods.

Design ideas

This is the AQS model:

AQS is mainly composed of three parts: state synchronization status, CLH queue composed of nodes, and ConditionObject condition variables (including conditional one-way queue composed of nodes).

State is decorated with volatile to ensure the visibility of our operations. Therefore, any thread that obtains the state through getState() can get the latest value, but setState() cannot guarantee atomicity. Therefore, AQS provides us with compareAndSetState method to realize atomicity by using the CAS function of the underlying UnSafe.

For AQS, the key to thread synchronization is the operation of state. It can be said that the success of obtaining and releasing resources is determined by state. For example, state > 0 means that resources can be obtained, otherwise they cannot be obtained. Therefore, the specific semantics of state are defined by the implementer. The existing ReentrantLock, ReentrantReadWriteLock, Semaphore The state semantics defined by CountDownLatch are different.

  • The state of ReentrantLock is used to indicate whether there is a lock resource. The variable records the number of lock reentries

  • The upper 16 bits of ReentrantReadWriteLock state represent the read lock state, and the lower 16 bits represent the write lock state

  • Semaphore's state is used to indicate the number of available signals

  • The state of CountDownLatch is used to represent the value of the counter

AQS implements two types of queues: synchronous queue and conditional queue.

Synchronization queue serves for thread blocking and waiting to obtain resources, while condition queue serves for thread entering the waiting state because a condition is not satisfied. The thread in the condition queue has actually obtained resources, but there are no conditions that can continue to execute, so it is entered into the condition queue and releases the held resources to allow other threads to execute. If the conditions are met at a certain time in the future, the thread will be transferred from the condition queue to the synchronization queue and continue to compete for resources to continue to execute downward.

Synchronization queue

CLH

Synchronization queue is a two-way queue based on linked list. It is also a variant of CLH lock. CLH lock is the basis of AQS queue synchronizer implementation.

Take a look at the CLH queue

  • CLH Lock is a lock invented by Craig, Landin, and Hagersten. It takes the initials of three people's names, so it is called CLH Lock.

  • CLH lock is a spin lock. Can ensure no hunger. Fairness in providing first come, first served services.

  • CLH queue lock is also a scalable, high-performance and fair spin lock based on linked list. The application thread spins only on local variables. It constantly polls the state of the precursor. If it is found that the precursor releases the lock, the spin ends.

Node

The synchronization queue Node is defined in the form of AQS internal class Node. This is the first inner class I saw earlier.

static final class Node {

    /** Schema definition  */

    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;

    /** Thread state  */

    static final int CANCELLED = 1;
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /** Thread waiting state  */
    volatile int waitStatus;

    /** Precursor node  */
    volatile Node prev;
    /** Post node  */
    volatile Node next;

    /** Thread object held  */
    volatile Thread thread;

    /** For the exclusive mode, point to the next node in the CONDITION waiting state; For SHARED mode, it is a SHARED node  */
    Node nextWaiter;

    // ...  Omit method definition
}

Node is a variant of CLH. CLH is a one-way queue, which is mainly characterized by spin checking the locked state of the precursor node. The AQS synchronization queue is a two-way queue, and each node also has a state waitStatus. Instead of always judging the state spin of the precursor node, it blocks and gives up the cpu time slice (context switching) after spinning for a period of time, waiting for the precursor node to actively wake up the successor node.

waitStatus has the following 5 statuses:

  • CANCELLED = 1 indicates that the current node has cancelled scheduling. When it times out or is interrupted (in response to an interrupt), it will trigger to change to this state, and the node after entering this state will not change again.

  • SIGNAL = -1 indicates that the successor node is waiting for the current node to wake up. When the successor node joins the queue, the status of the predecessor node will be updated to SIGNAL.

  • CONDITION = -2 indicates that the node is waiting on the CONDITION. When other threads call the signal() method of the CONDITION, the node in CONDITION status will be transferred from the waiting queue to the synchronization queue, waiting to obtain the synchronization lock.

  • In the PROPAGATE = -3 sharing mode, the predecessor node will wake up not only its successor nodes, but also the successor nodes.

  • INITIAL = 0 is the default status when new nodes join the queue.

As can be seen from the above code, the threads in the CLH linked list are waiting for resources in two modes, SHARED and EXCLUSIVE, where SHARED represents the SHARED mode and EXCLUSIVE represents the EXCLUSIVE mode. The main difference between SHARED mode and EXCLUSIVE mode is that only one thread can obtain resources at the same time, while SHARED mode can have multiple threads obtain resources at the same time. A typical scenario is a read-write lock. Multiple threads can obtain read lock resources at the same time in a read operation, while only one thread can obtain write lock resources at the same time in a write operation, and other threads will be blocked when trying to obtain resources.

Main behaviors of synchronization queue

The head and tail fields of AQS class member variables point to the head and tail nodes of the synchronization queue respectively:

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;


The head represents the head node of the synchronization queue and the tail represents the tail node of the synchronization queue. The specific organization form is as follows:

When the acquire method of AQS is called to obtain resources, if the resources are insufficient, the current thread will be encapsulated as a Node node and added to the end of the synchronization queue (queue). The head Node is used to record the thread Node currently holding resources, and the successor Node of the head is the next thread Node to be scheduled. When the release method is called, The thread on the Node will be awakened (dequeued) and continue to obtain resources.

The main behaviors of synchronization queue are: entering and leaving the queue

Join the team

The thread that fails to acquire resources needs to be encapsulated into a Node node, and then the tail is queued. The addWaiter function is provided in AQS to complete the creation and queueing of Node nodes. When adding a Node, if the CLH queue already exists, quickly add the current Node to the end of the queue through CAS. If the addition fails or the queue does not exist, initialize the synchronization queue.

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    Node node = new Node(mode);

    for (;;) {
        Node oldTail = tail;
        if (oldTail != null) {
            node.setPrevRelaxed(oldTail);
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
        } else {
            initializeSyncQueue();
        }
    }
}

Summary: after entering the queue, the thread failed to obtain the lock. After entering the queue, add the new node to the tail, then perform CAS operation on the tail, and move the tail pointer to the new node.

Out of the team

All nodes in CLH queue are thread nodes that fail to acquire resources. When the thread holding resources releases resources, it will wake up the thread node pointed to by head.next (the second node of CLH queue). If the awakened thread node acquires resources successfully, the thread node emptying information will be set as the head node (new sentinel node) and the original head node out of the queue (original sentinel node)

  protected final boolean tryRelease(int releases) {
        int c = getState() - releases; 
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { //  If   state=0   The lock can be released
            free = true; 
            setExclusiveOwnerThread(null); //  Set the lock taking thread to   null
        }
        setState(c); //  Reset synchronizer   state
        return free; //  Returns whether the release was successful
  }

  private void unparkSuccessor(Node node) {
    //  node   The node is the node that currently releases the lock and is also the head node of the synchronization queue
    int ws = node.waitStatus;
    //  If the node has been cancelled, set the state of the node to initialization
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //  Take out team two   s
    Node s = node.next;
    //  s   If it is empty, it means   node   The last node of is empty
    //  s.waitStatus   greater than   0, for   s   The node has been cancelled
    //  In these two cases, start from the end of the team, traverse forward and find the first one   waitStatus   Field is not cancelled
    if (s == null || s.waitStatus > 0) {
        s = null;
     
        //  The end condition is that the preceding node is   head   Yes
        for (Node t = tail; t != null && t != node; t = t.prev)
            //  t.waitStatus  <=  0   explain   t   It has not been cancelled at present. It must be waiting to be awakened
            if (t.waitStatus <= 0)
                s = t;
    }
    //  Wake up the thread found by the above code
    if (s != null)
        LockSupport.unpark(s.thread);
}


When the thread is awakened, it continues to execute the acquirequeueueueued method and enters the loop

 /**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            //Get precursor node
            final Node p = node.predecessor();
            //If the precursor node is the first node, obtain resources (subclass Implementation)
            if (p == head && tryAcquire(arg)) {
                //The resource is obtained successfully. Set the current node as the head node, clear the information of the current node, and turn the current node into a sentinel node
                setHead(node);
                //The original first node points to the next node   null
                p.next = null; // help GC
                //Return thread interrupt status
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}

Summary: after leaving the queue, the lock is released to wake up the successor node of the head. The successor node of the head wakes up from the blocking and starts to grab the lock to obtain the lock successfully. At this time, the head pointer moves back one position, and the successor node of the original head becomes a new head.

Finally, an overview of the synchronization queue process is given

Conditional queue

An AQS can correspond to multiple conditional variables

ConditionObject internally maintains a one-way condition queue, which is different from the CLH queue. The condition queue only queues the thread nodes that execute await, and the nodes that join the condition queue cannot be in the CLH queue. The nodes that exit the condition queue will join the CLH queue.

When a thread executes the await function of ConditionObject and blocks the current thread, the thread will be encapsulated as a Node node and added to the end of the condition queue. Other threads execute the signal function of ConditionObject and transfer the thread Node at the head of the condition queue to the CLH queue to participate in competing resources. The specific process is as follows:

A Condition object has a single waiting task queue. In a multithreaded task, we can new out multiple waiting task queues. For example, we new out two waiting queues.

 private Lock lock = new ReentrantLock();
 private Condition FirstCond = lock.newCondition();
 private Condition SecondCond = lock.newCondition();


Therefore, the real AQS task is generally a task queue with N waiting queues. Therefore, we try to call signal instead of signalAll, because only one of the specified instantiated waiting queues can get the lock.

Design pattern

From the perspective of design patterns, AbstractQueuedSynchronizer is an abstract class. All classes using methods should inherit several methods of this class, and the corresponding design pattern is template pattern. In this way, a large number of details involved in the implementation of synchronizer are solved, and the implementation work can be greatly reduced.

Several methods are those described in the note translation above:

  • tryAcquire

  • tryRelease

  • tryAcquireShared

  • tryReleaseShared

  • isHeldExclusively

Explain the function of each method:

  • tryAcquire: attempts to acquire resources in exclusive mode. If the acquisition is successful, it returns true; otherwise, it returns false.

  • tryRelease: attempts to release resources in exclusive mode. If the release is successful, it returns true; otherwise, it returns false.

  • Tryacquisureshared: try to obtain resources in shared mode. If a positive number is returned, it indicates that the acquisition is successful and there are still available remaining resources; If 0 is returned, the acquisition is successful, but there are no available remaining resources; If a negative number is returned, the resource acquisition failed.

  • Tryrereleaseshared: attempts to release resources in shared mode. If the release is successful, it returns true; otherwise, it returns false.

  • Ishldexclusively: judge whether the current thread is monopolizing resources. If yes, return true; otherwise, return false.

According to the function, the method implementation in AbstractQueuedSynchronizer can be divided into two categories: acquire and release. At the same time, exclusive mode and shared mode are distinguished

You can see that the specific implementation class needs to define the acquisition and release of resources in different modes. For example, you can take a look at the internal class Sync in ReentrantReadWriteLock.

Current thread with lock: exclusiveOwnerThread

Self realization

AQS defines a set of synchronization templates for multi-threaded access to shared resources, which solves a lot of details involved in the implementation of synchronizer and can greatly reduce the implementation work. Now we implement a non reentrant exclusive lock based on AQS and directly use the exclusive template provided by AQS. We only need to clarify the semantics of state and implement tryAcquire and tryRelease functions (obtaining and releasing resources). Here, state 0 means that the lock is not held by a thread, and state 1 means that the lock has been held by a thread. Because it is a non reentrant lock, it is not necessary to record the number of times the lock is obtained by the thread holding the lock.

The non reentrant exclusive lock code is as follows

public class NonReentrantLock implements Lock {

    /**
     * 
     * @Description Custom synchronizer
     */
    private static class Sync extends AbstractQueuedSynchronizer {

        /**
         * Is the lock held by the thread
         */
        @Override
        protected boolean isHeldExclusively() {
            //0: not held 1: held
            return super.getState() == 1;
        }

        /**
         * Acquire lock
         */
        @Override
        protected boolean tryAcquire(int arg) {
            if (arg != 1) {
                //To obtain a lock, you need to   state   Update to   1, so   arg   Must be   one
                throw new RuntimeException("arg not is 1");
            }
            if (compareAndSetState(0, arg)) {//cas updates state to 1 successfully, which means that the lock is obtained successfully
                //Set hold lock thread
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /**
         * Release lock
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (arg != 0) {
                //To release the lock, you need to   state   Update to   0, so   arg   Must be   0
                throw new RuntimeException("arg not is 0");
            }
            //Empty thread holding lock
            setExclusiveOwnerThread(null);
            //set up   state   Status is   0, not here   cas, because only the thread that successfully obtains the lock will execute this function, and thread safety does not need to be considered
            setState(arg);
            return true;
        }

        /**
         * Provides an entry to create a conditional variable
         */
        public ConditionObject createConditionObject() {
            return new ConditionObject();
        }

    }

    private final Sync sync = new Sync();

    /**
     * Acquire lock
     */
    @Override
    public void lock() {
        //Aqs   Exclusive - get resource template function
        sync.acquire(1);
    }
        
    /**
     * Acquire lock - response interrupt
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        //Aqs   Exclusive - get resource template function (response thread interrupt)
        sync.acquireInterruptibly(1);
    }

    /**
     * Successful lock acquisition - no blocking
     */
    @Override
    public boolean tryLock() {
        //Subclass implementation
        return sync.tryAcquire(1);
    }
    
    /**
     * Acquire lock timeout mechanism
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        //Aqs   Exclusive - get resource template function (timeout mechanism)
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }
    
    /**
     * Release lock
     */
    @Override
    public void unlock() {
        //Aqs   Exclusive - release resource template function
        sync.release(0);
    }
    
    /**
     * Create condition variable
     */
    @Override
    public Condition newCondition() {
        return sync.createConditionObject();
    }
}

NonReentrantLock defines an internal class Sync, which is used to implement specific lock operations. It inherits AQS. Because it uses an exclusive template, it overrides the tryAcquire and tryRelease functions. In addition, it provides an entry to create condition variables. Next, use a custom exclusive lock to synchronize the two thread pairs j + +.

    private static int j = 0;

    public static void main(String[] agrs) throws InterruptedException {
        NonReentrantLock  nonReentrantLock = new NonReentrantLock();

        Runnable runnable = () -> {
            //Acquire lock
            nonReentrantLock.lock();
            for (int i = 0; i < 100000; i++) {
                j++;
            }
            //Release lock
            nonReentrantLock.unlock();
        };

        Thread thread = new Thread(runnable);
        Thread threadTwo = new Thread(runnable);

        thread.start();
        threadTwo.start();

        thread.join();
        threadTwo.join();

        System.out.println(j);
    }
    

No matter how many times it is executed, the output content is:
200000


other

LockSupport helper class

LockSupport is a thread blocking tool class. All methods are static methods. Threads can be blocked at any position. Of course, there must be a wake-up method after blocking.

Park is because park means parking in English. If we think of Thread as a car, park is to stop the car, and unpark is to start the car and run.

park/unpark calls the native code in Unsafe (providing CAS operations).

explain

This article refers to a lot of network pictures and article materials. Strictly speaking, it is not an original article. If the content cited in this article is infringing, please contact to delete it.

reference resources

  • https://blog.csdn.net/lpf463061655/article/details/87290730

  • https://www.codenong.com/cs106963035/

  • https://mp.weixin.qq.com/s/bxWgo9IuggDpE1l37JqEhQ

  • https://www.modb.pro/db/108644

  • https://mp.weixin.qq.com/s/BLnZYa4lbXUx3KEQm7Z7tA

  • https://mp.weixin.qq.com/s/Y4GbMdNmSDvHtomxtObRSg

  • https://www.baiyp.ren/CLH%E9%98%9F%E5%88%97%E9%94%81.html

  • https://juejin.cn/post/6873020483755884552

  • https://mp.weixin.qq.com/s?__biz=MzU4NzU0MDIzOQ==&mid=2247488891&idx=1&sn=227928446c692aaa0085557682ed732d&scene=21#wechat_redirect

Small box technology sharing

Sharing of programming and technical knowledge and life perception

82 original content

official account

Posted by Red Blaze on Wed, 03 Nov 2021 01:25:18 -0700