Java concurrent programming summary 6: locks framework, AQS detailed

Because it is preparing for the school recruitment interview, this knowledge is no longer a beginner, and it is too slow to organize it as a beginner;

Decided to look directly at the blog I wrote before, here is only a framework of knowledge combing to facilitate the formation of a structure;

If there are any missing knowledge points, then make detailed supplements;

 

Java concurrent programming (5): Detailed explanation of Lock, AbstractQueuedSynchronizer

1. About lock:

0, concurrent package architecture:

1. Locks package architecture and introduction:

2. Simple comparison between lock and synchronized

  • Lock is an interface, the implementation of the code level, synchronized is the keyword, is the built-in language implementation (JVM level).
  • Lock is displayed to obtain the release of the lock, more scalable, synchronized is implicitly acquire the lock is released, more simple.
  • When an exception occurs in Lock, if the lock is not actively released through unlock() , it is likely to cause a deadlock phenomenon. Therefore, when using Lock, you need to release the lock in the finally block. When an exception occurs, synchronized will automatically release the lock held by the thread. , So it will not cause deadlock.
  • Lock allows the thread waiting for the lock to respond to the interrupt , and when using synchronized, the thread waiting for the lock will wait forever and cannot respond to the interrupt .
  • Lock can try to acquire the lock non-blocking, interruptible, and timeout; synchronized cannot.
  • Lock can know whether it has successfully acquired the lock, but synchronized cannot know.

3. Common methods of Lock structure: lock(), unlock(), lockInterruptibly(), tryLock()

4. Briefly mention ReadWriteLock:

The ReadWriteLock interface is a separate interface (not inherited from the Lock interface) , which provides methods for acquiring read locks and write locks.

The so-called read-write lock is a pair of related locks-read lock and write lock. Read locks are used for read-only operations, and write locks are used for write operations. The read lock can be held by multiple threads at the same time, while the write lock is exclusive and can only be acquired by one thread.

The locks we mentioned before (including the Lock interface and synchronized keyword) are all exclusive locks. These locks allow only one thread to access at the same time. The read-write lock can allow multiple reader threads to access at the same time, but when the writer thread accesses, all reader threads and other writer threads are blocked. The read-write lock maintains a pair of locks, a read lock and a write lock. By separating the read lock and the write lock, the concurrency is greatly improved compared to the general exclusive lock.

Under normal circumstances, the performance of read-write locks will be better than exclusive locks, because most scenarios read more than write. In the case of more reads than writes, read-write locks can provide better concurrency and throughput than exclusive locks.

The implementation of the read-write lock provided by the Java concurrency package is ReentrantReadWriteLock .

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

The realization of the read-write lock itself is far more complicated than the exclusive lock. Therefore, the read-write lock is more suitable for the following situations:

  • High-frequency read operations, relatively low-frequency write operations;
  • The time used for the read operation will not be too short. (Otherwise, the overhead caused by the complex implementation of the read-write lock itself will become the main consumption cost).

 

 

 

2. About AQS:

1. The meaning of synchronizer:

The synchronizer is used to build the basic framework of locks and other synchronization components. Its implementation mainly relies on an int member variable to represent the synchronization state and a FIFO queue to form a waiting queue. Its subclasses must rewrite several protected methods of AQS that are modified to change the synchronization state . Other methods mainly implement queuing and blocking mechanisms. The update of the state uses the three methods of getState, setState and compareAndSetState .

The subclass is recommended to be defined as a static internal class of the custom synchronization component . The synchronizer itself does not implement any synchronization interface. It just defines a number of synchronization state acquisition and release methods for the use of the custom synchronization component. The synchronizer supports both Exclusive acquisition of synchronization status can also support shared acquisition of synchronization status, so that different types of synchronization components can be easily implemented.

The synchronizer is the key to the realization of the lock (or any synchronization component). The synchronizer is aggregated in the realization of the lock, and the synchronizer is used to realize the semantics of the lock. The relationship between the two can be understood in this way: the lock is user-oriented, it defines the interface between the user and the lock, and hides the implementation details; the synchronizer is the implementer of the lock, which simplifies the implementation of the lock and shields the synchronization Low-level operations such as state management, thread queuing, waiting and waking up . Locks and synchronizers are a good separation of the areas of concern for users and implementers.

2. Summary:

  1. The implementation of synchronization components (not only locks, but also CountDownLatch, etc.) depends on the synchronizer AQS . In the implementation of synchronization components, the use of AQS is recommended to define static memory classes that inherit AQS;
  2. AQS adopts the template method for design. The protected modification method of AQS needs to be rewritten and implemented by the subclass inheriting AQS . When the method of the subclass of AQS is called, the rewritten method will be called;
  3. AQS is responsible for the management of synchronization state, queuing of threads, waiting and waking up these low-level operations , while synchronization components such as Lock mainly focus on achieving synchronization semantics ;
  4. When rewriting the AQS method, use the method provided by AQS to modify the synchronization status;getState(),setState(),compareAndSetState()

3. The template methods provided by AQS can be divided into 3 categories:

  1. Exclusive acquisition and release of synchronization status;
  2. Shared acquisition and release of synchronization status;
  3. Query the status of waiting threads in the synchronization queue;

4. The realization angle of synchronization components:

4.1. The perspective of the implementer of the synchronization component:

Through rewriteable methods: exclusive : tryAcquire() (exclusive acquisition of synchronization status), tryRelease() (exclusive release of synchronization status);

Shared  : tryAcquireShared() (shared acquisition of synchronization status), tryReleaseShared() (shared release of synchronization status);

4.2, the angle of AQS

For AQS, you only need to synchronize the true and false returned by the component, because AQS will have different operations on true and false. True will consider the current thread to obtain the synchronized component successfully and return directly, and if it is false, AQS will also A series of methods such as inserting the current thread into a synchronized queue.

In general, the synchronization component achieves the synchronization semantics it wants to express by rewriting the AQS method, and AQS only needs the true and false expressed by the synchronization component. AQS will do different processing for the different situations of true and false. As for the underlying implementation, you can read this article . (See below for detailed explanation)

5. Deep understanding of AQS

5.1. Exclusive lock:

  • void acquire(int arg): Exclusively acquire the synchronization status, if the acquisition fails, insert the synchronization queue to wait;
  • void acquireInterruptibly(int arg): Same as acquire method, but interrupt can be detected when waiting in the synchronous queue;
  • boolean tryAcquireNanos(int arg, long nanosTimeout): The timeout waiting function is added on the basis of acquireInterruptibly. If the synchronization state is not obtained within the timeout period, it returns false;
  • boolean release(int arg): Release the synchronization state, this method will wake up the next node in the synchronization queue;

Shared lock:

  • void acquireShared(int arg): Shared acquisition of synchronization status, the difference from exclusive type is that multiple threads acquire synchronization status at the same time;
  • void acquireSharedInterruptibly(int arg): Add the function of responding to interrupt on the basis of acquireShared method;
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout): Added the function of timeout waiting on the basis of acquireSharedInterruptibly;
  • boolean releaseShared(int arg): shared release synchronization status

5.2. Synchronization queue:

The synchronization queue in AQS is implemented in a chained manner .

There is a static inner class Node in AQS , which has some properties:

volatile int waitStatus //Node status
volatile Node prev //Predecessor node of the current node/thread
volatile Node next; //The successor node of the current node/thread
volatile Thread thread;//Thread reference to the synchronization queue
Node nextWaiter; //Waiting The next node in the queue

Obviously: this is a two-way queue .

Brief summary:

  1. The data structure of the node, that is, the static internal class Node of AQS, the waiting state of the node and other information ;
  2. The synchronization queue is a two-way queue, and AQS manages the synchronization queue by holding head and tail pointers ;

5.3, exclusive lock

How do nodes enter and leave the team? In fact, this corresponds to the two operations of acquiring and releasing the lock : failing to acquire the lock and proceeding to enqueue operation , and successfully acquiring the lock and proceeding to dequeue operation .

Exclusive lock acquire acquire ():

  • Success: the method ends and returns;
  • Failure: first call addWaiter () (the CAS end is inserted ), and then call the acquireQueued () method

Exclusive lock release release():

The above two can be summarized:

  1. The thread fails to acquire the lock, and the thread is encapsulated as a Node for enqueue operation. The core method is addWaiter() and enq(). At the same time, enq() completes the initialization of the head node of the synchronization queue and the retry of the CAS operation failure ;
  2. The thread acquiring lock is a spinning process. If and only if the predecessor node of the current node is the head node and successfully obtains the synchronization state, the node dequeues, that is, the thread referenced by the node obtains the lock, otherwise, when the condition is not met, Will call the LookSupport.park() method to block the thread ;
  3. When the lock is released, subsequent nodes will be awakened;

Generally speaking: when acquiring the synchronization status, AQS maintains a synchronization queue, and the thread that fails to obtain the synchronization status will be added to the queue to spin; the condition for removing the queue (or stopping the spinning) is that the precursor node is the head node and The synchronization status was successfully obtained. When releasing the synchronization state, the synchronizer will call the unparkSuccessor() method to wake up subsequent nodes.

5.4. Shared lock:

Acquire the shared lock acquireShared () :

The logic is almost exactly the same as the acquisition of an exclusive lock. The condition for exiting during the spin process is that the predecessor node of the current node is the head node and the return value of tryAcquireShared(arg) is greater than or equal to 0 to successfully obtain the synchronization state .

The release of the shared lock releaseShared():

It is a bit different from the exclusive lock release process. In the shared lock release process, for concurrent components that can support simultaneous access by multiple threads, it must be ensured that multiple threads can safely release the synchronization state. The CAS guarantees used here . CAS operation failed continue, retry in the next cycle.

 

Guess you like

Origin blog.csdn.net/ScorpC/article/details/113857403