Detailed explanation of Java synchronization framework abstractqueuedsynchronizer

Abstractqueuedsynchronizer overview

Abstractqueuedsynchronizer is a very important framework class in Java. It implements the core semantics of multi-threaded synchronization. As long as we inherit abstractqueuedsynchronizer, we can easily implement our own thread synchronizer. The lock in Java is based on abstractqueuedsynchronizer. The following first shows some methods provided by the abstractqueuedsynchronizer class:

Abstractqueuedsynchronizer class method

In terms of class structure, abstractqueuedsynchronizer inherits abstractownablesynchronizer. The only two methods of abstractownablesynchronizer are to provide thread settings of the current exclusive mode:

Exclusiveownerthread represents the thread that is currently synchronized. Because it is in exclusive mode, any synchronization request of other threads will not be satisfied during the synchronization held by exclusiveownerthread.

So far, it should be noted that abstractqueuedsynchronizer supports not only synchronization in exclusive mode, but also synchronization in shared mode. There are differences between shared locks and exclusive locks in the implementation of Java locks, and these implementations are based on the support of abstractqueuedsynchronizer for shared synchronization and exclusive synchronization. From the methods provided by abstractqueuedsynchronizer shown above, we can find that the API of abstractqueuedsynchronizer can be roughly divided into three categories:

The above three types of APIs are divided into exclusive and shared. We can use appropriate APIs for multithreading synchronization according to our needs.

The following is an example of inheriting abstractqueuedsynchronizer to implement its own synchronizer:

The functions implemented by mutex are: use 0 to represent that the synchronization variable can be obtained, and use 1 to represent that the synchronization variable needs to be obtained after being released. This is a simple exclusive lock implementation. At any time, only one thread will obtain the lock, and other threads requesting to obtain the lock will block the wait until the lock is released, and the waiting threads will compete again to obtain the lock. Mutex gives us a good example. To implement our own thread synchronizer, we inherit abstractqueuedsynchronizer to implement its three abstract methods, and then use this implementation class to perform lock and unlock operations. It can be found that abstractqueuedsynchronizer framework paves the way for us, We only need to make a little change to achieve efficient and safe thread synchronization. The following will analyze how abstractqueuedsynchronizer provides us with such a powerful synchronization capability.

Abstractqueuedsynchronizer implementation details

Exclusive Mode

Abstractqueuedsynchronizer uses a volatile int as the synchronization variable. Any thread that wants to obtain a lock needs to compete for the variable. The thread that obtains the lock can continue the execution of the business process, while the thread that does not obtain the lock will be placed in a FIFO queue and wait for the synchronization variable to compete again to obtain the lock. Abstractqueuedsynchronizer encapsulates each thread that does not obtain a lock into a node and then puts it into the queue. Let's analyze the data structure of node first:

The above shows the four states defined by node. It should be noted that only one state is greater than 0, that is, cancelled, which is cancelled. There is no need to coordinate the competition of synchronization variables for this thread. See the notes for the meaning of other. As mentioned in the previous section, abstractqueuedsynchronizer provides exclusive and shared modes. Abstractqueuedsynchronizer uses the following two variables to mark whether the mode is shared or exclusive:

Interestingly, the node uses a variable nextwaiter to represent two meanings. In exclusive mode, nextwaiter represents the next node waiting on the conditionobject. In shared mode, it is shared, because it is impossible for any synchronizer to realize both sharing and exclusive modes at the same time. A more professional explanation is:

Abstractqueuedsynchronizer uses a two-way linked list to manage the nodes requesting synchronization. It saves the head and tail of the linked list. The new node will be inserted at the end of the linked list, and the head of the linked list always represents the thread that obtains the lock. After the thread at the head of the chain releases the lock, it will notify subsequent threads to compete for shared variables. Let's analyze how abstractqueuedsynchronizer implements acquire and release in exclusive mode.

First, use the method acquire (int) to compete for synchronization variables. The following is the calling link:

First, the method tryacquire will be called to try to obtain the lock. The method tryacquire needs subclasses to implement. The implementation of subclasses is nothing more than operating the synchronization variable state through compareandsetstate, getstate and setstate. The implementation of subclasses needs to be implemented according to their respective demand scenarios. Continue to analyze the above acquire process. If tryacquire returns true, that is, the value of state has been successfully changed, that is, the synchronization lock has been obtained, then you can exit. If false is returned, it indicates that other threads have obtained locks. At this time, abstractqueuedsynchronizer will use addwaiter to add the current thread to the end of the waiting queue and wait for competition again. Note that the current thread is marked as exclusive. Then the play comes. The method acquirequeueueueueueueueued enables the newly added node to continuously poll in a for loop, that is, spin. The conditions for the exit of the acquirequeueueueueueueueueueued method are:

After the above two conditions are met, the node will obtain the lock. According to the provisions of abstractqueuedsynchronizer, the node that obtains the lock must be the head node of the linked list. Therefore, it is necessary to set the current node as the head node. What happens to nodes that do not meet the above two conditions? Look at the second branch of the for loop. First, shouldparkafterfailedacquire method. The name should mean to judge whether the current thread should be Park, and then the method parkandcheckinterrupt. This method will be used only if shouldparkafterfailedacquire returns true, which means to judge whether the node needs to be park first. If necessary, Then park it. As for the park and unpark of threads, abstractqueuedsynchronizer uses the underlying technology to implement, so it will not be analyzed here. Now let's analyze under what circumstances a node will be Park (block):

It can be found that only when the state of the precursor node of the node is node It will return true only when it is signal, that is, only the status of the current drive node changes to node Signal will notify the current node, so if the predecessor node is a node Signal, then the current node can park safely. After completing the work, the predecessor node will unpark its successor node when releasing resources. Let's take a look at the release process:

First, the tryrelease method is used to ensure the security of resources. After the complete release, if the state of the node is found to be less than 0, it will change to 0. 0 represents the initialization state. The current thread has completed its work. After releasing the lock, it will return to its original state. Then, the successor node of the node will be obtained. If there is no successor node or the successor node has been cancelled, find the first qualified node from the tail and unpark it.

The above describes a pair of acquire release. If you want the thread to be interrupted during contention, you can use acquire interrupt. If you want to add a time limit for obtaining locks, you can use the tryacquirenanos (int, long) method to obtain locks.

Sharing mode

Like the exclusive mode, analyze the acquireshared process:

The process of obtaining locks is as follows:

Release process in sharing mode:

First, try to use the tryrereleaseshared method to release resources. If the release fails, false will be returned. If the release succeeds, continue to execute the doreleaseshared method to wake up subsequent nodes to compete for resources. It should be noted that the difference between shared mode and exclusive mode is that exclusive mode allows only one thread to obtain resources, while shared mode allows multiple threads to obtain resources. Therefore, in the exclusive mode, we can determine to obtain resources only when tryacquire returns true. In the shared mode, we can obtain resources as long as the return value of tryacquireshared is greater than or equal to 0. Therefore, you should ensure that the requirements you need to implement are consistent with those expected by abstractqueuedsynchronizer.

Like bucket exclusive mode, sharing mode has two other APIs:

This article roughly describes some basic situations of the abstractqueuedsynchronizer framework. The specific details are not studied in depth, but abstractqueuedsynchronizer, as the underlying support of lock implementation in Java, needs to be studied. In the future, we will analyze the implementation of various locks in Java based on abstractqueuedsynchronizer.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support programming tips.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>