For Java and package from Lock Lock, AQS, exclusive Lock, shared Lock, LockSupport tool, Condition interface to analyze Java and package knowledge.
The Lock Lock
For Lock locks, it is an interface provided by Java to developers for use in multi-threaded situations, used to complete the Lock function. Before Java SE5, the Lock function was realized by synchronized, and then the Lock Lock was added. Compared with synchronized, Lock Lock is explicitly used by developers, and methods in Lock Lock need to be called when obtaining and releasing locks. The process of acquiring and releasing locks is visible to developers, while synchronized implicitly acquires and releases locks. For synchronized modified methods or code blocks, when the thread method reaches the modified method or code block, it will acquire the monitor lock of the object according to the bound object to complete the function of acquiring locks. This is not visible to the developer. The difference between the two shows that Lock Lock is more flexible for the developer. Lock acquisition and Lock release are completely realized by the developer. Meanwhile, Lock acquisition in interruptable and timeout cannot be provided by synchronized keyword.
At the same time, from the perspective of usage, Lock Lock is the interface provided by Java and is used from the API level, while synchronized keyword is used from the JVM level. The locking and unlocking of synchronized are realized by JVM.
When using a Lock Lock, finally code block is usually added to the logic to realize the Lock release logic, so as to ensure that the Lock will be released regardless of the success of the logical code and avoid deadlock. At the same time, when using a Lock Lock, it is not recommended to execute the Lock acquisition logic in the try code block. If an exception occurs in obtaining the lock, the lock will be released for no reason.
Lock Lock provides three features that synchronized does not have, namely, try non-blocking Lock acquisition, interrupted Lock acquisition and timeout Lock acquisition.
Try to acquire the lock without blocking: For synchronized if one thread locks the Lock has been taking up this thread will be blocked in the synchronous queue, for the Lock locks, you can try to obtain the Lock, is a thread to acquiring a Lock is call attempts to acquire Lock method, try to get a moment, if the moment there is no other thread holds the Lock, The thread acquires and holds the lock. If it does not acquire the lock, it will give up the lock without entering the blocking queue, preventing the thread from blocking.
Interrupted lock acquisition: Unlike synchronized, the thread that acquired the lock can respond to the interrupt. When the thread that acquired the lock is interrupted, the interrupt exception will be thrown and the lock will be released.
Timeout lock acquisition: Timeout lock acquisition is better understood as a time range set when the lock is acquired. If the time range is exceeded, then the return will not continue to acquire the lock, preventing blocking.
Lock interface methods include Lock, tryLock, and unlock.
AQS
Abstract queue is the translation of AQS full name AbstractQueueSynchronizer, synchronizer, its main function is to build a lock as well as to the basis of the synchronous component framework, For example, synchronous queues such as ReenstrantLock, ReadWriteReentrantLock, CountDownLatch and Semphore inherit AQS. AQS is the basic framework to realize these locks and synchronization components, on the basis of AQS to develop their own locks and synchronization components.
When we use AQS, it is mainly realized through inheritance. Subclasses inherit AQS and implement abstract methods to manage synchronization state. Therefore, when we need to manage synchronization state, we need to operate through getState, setState and compareAndSetState methods in AQS. This ensures that the synchronization state is safe.
When we go to achieve the general static internal class to inherit AQS, because we can know by looking at the SOURCE code of AQS, AQS did not achieve any synchronization interface, it is only defined some methods to obtain and operate the synchronization state, so to achieve synchronization function needs to be in the subclass of the static internal class itself. In addition, AQS supports exclusive ReentrantLock and shared ReentrantReadWriteLock.
In the case of ReentrantLock, Sync is implemented in the ReentrantLock class. The inner class inherits AQS and implements synchronization methods. In ReentrantLock, the interface for users to operate the lock is defined, and details are hidden (you can call the lock method directly in normal use). AQS is for the implementor of the lock (by defining the Sync internal class to inherit AQS is the implementor of the lock), through AQS simplifies the implementation of the lock, and for the implementor, the synchronization queue state management, thread queuing, thread waiting, wake up and other low-level operations are shielded. This isolates the user’s domain from the implementor’s, with the user concerned with usage and the implementor concerned with the implementation of the lock.
View AQS source can know which implements the two sets of synchronization method, respectively is exclusive and Shared and exclusive similar to acquire this, while Shared generally in the final add Shared tag is Shared synchronization method, this method can according to their own needs when their implementation is exclusive or Shared rewrite the corresponding method respectively.
AQS includes synchronization queue, exclusive synchronization state acquisition and release, shared synchronization state acquisition and release and other implementation methods.
Synchronous queue
For the synchronization queue, it is a FIFO two-way queue, its main function is to manage the synchronization state. When a thread fails to obtain the synchronization state, AQS will construct a Node (Node Node) of the thread and its waiting state and add it to the synchronization queue. When the synchronization state is released, AQS will wake up the thread in the first Node in the synchronization queue and make it try to obtain the synchronization state again.
Therefore, in AQS, the synchronization queue mainly acts as a buffer. It stores threads that fail to obtain synchronization status and their corresponding status, waits for the synchronization status to be released, and the nodes in the synchronization queue will wake up again and try to obtain synchronization status again.
Synchronous queue is a bidirectional queue, follow FIFO, then ensure the Node Node is inserted into the synchronous queue order, so the insertion of synchronous queue operations must also be thread-safe, and AQS in guarantee insert synchronous queue operation thread safe CAS algorithm was used to set the end Node (because the addition of each Node is added to the end Node). It can be seen from the source code that the method is implemented as compareAndSetTail(Node expect,Node Update) method, which needs to pass the current tail Node and the need to update the tail Node, to ensure that the tail Node is the desired Node. Because FIFO is followed, the first node is the one that succeeded in obtaining the synchronization state, and the subsequent node becomes the first node.
Exclusive synchronization state acquisition and release
Exclusive means that only one thread can get the synchronization state at a time, and the other threads will block until the thread that got the synchronization state releases the synchronization state. How does AQS achieve this approach?
The acquire(int arg) method is called to obtain the synchronization status
This method knows that tryAcquire(which is implemented through a custom synchronizer) is first called, that the acquisition of the synchronization state is thread-safe and that if the acquisition fails, the addWaiter method is called to add the thread to the end of the synchronization queue, Finally, the node is spun through the acquireQueued method to loop the synchronization state.
In order to ensure that Node nodes are added in sequence, AQS calls enQ method to ensure the sequence. In ENQ method, nodes are added to the synchronization queue through an infinite loop. Only when CAS Node is set successfully, this method will return. Therefore, it can be seen that the ENQ method passes concurrently by serializing the node-addition request through CAS.
For exclusive access to release the lock, when acquiring the synchronization state, AQS maintains a synchronous queue, acquiring the synchronization status failed thread will be added to the synchronous queue, and in the queue will also try to obtain synchronization by means of spin state, could obtain the success to the synchronous state standard is whether the thread node is the first node, when the release of sync, Successor nodes are notified and can obtain the synchronization status of the release.
Obtaining and releasing shared synchronization state
Shared, as opposed to exclusive, can have multiple threads fetching synchronous state.
AcquireShared (int arg) in AQS you can share the state of the synchronization by calling the acquireShared(int arg) method
If the tryAcquireShared return value is greater than 0, the synchronization status can be obtained. Therefore, in the shared obtaining spin process, the synchronization state is successfully obtained and the spin exit is achieved only when the return value tryAcquireShared is greater than or equal to 0.
Reentrant lock
A reentrant lock, as its name implies, allows a thread to acquire the lock repeatedly. This ensures that a thread that acquires the lock will not be blocked by the lock.
When a thread acquires the lock again, the lock only needs to determine whether the thread currently occupying the lock is the same thread. If so, the lock is successfully acquired. The same thread that has acquired the lock multiple times needs to release the lock multiple times until the counter is 0.
Both ReentrantLock and synchronized lock support ReentrantLock. Of course, synchronized is still an implicit ReentrantLock. The process of ReentrantLock acquisition is invisible to the developer. The lock call will not block if the lock is acquired a second time.
Reentrant lock in synchronized is achieved through monitor lock, that is, the number of times the monitor lock is acquired. When synchronized modifiers a code block, moniterEnter counts the number of times the thread has acquired the lock, and moniterExit releases the lock. The same number of entries need to be released several times to complete the final release of the lock.
When a synchronized modification method is used, the ACC_SYNCHRONIZED identifier is used to obtain the monitor lock. The JVM will acquire the monitor lock if the ACC_SYNCHRONIZED identifier is detected in the method during compilation. If the method is entered multiple times, the monitor lock will be acquired multiple times. Also, the final lock must be released several times.
Fair and unfair locks
Fair lock and non-fair lock are relative concepts. Fair lock follows THE rules of FIFO, and the thread that enters the synchronization queue first obtains the lock, while non-fair lock does not have this limitation. When the synchronization state is released, all threads in the synchronization queue have the opportunity to obtain the synchronization state.
Take ReentrantLock as an example. ReentrantLock supports fair and unfair locks. It uses constructors to set fair and unfair locks.
You can see that ReentrantLock defaults to an unfair lock and sets the fair and unfair lock by passing the Boolean argument.
The implementation of an unfair lock is easy to understand. As long as the CAS(compareAndSetState method) is set successfully, the thread has successfully acquired the lock
On the other hand, the Fair lock is implemented using the HasqueuedToraise () method, which, when called, determines whether the current node has a precursor node. If there is a thread that indicates an earlier lock request in the synchronous queue, The thread continues to block, and if it does not, the node is a header and can be synchronized.
Read-write lock
ReentrantWriteReadLock is a read-write lock that supports both read locks and write locks, but there are some limitations. Read locks support multiple read locks. Write locks only support single-thread writes, and if one thread has acquired the write lock, other threads will also block the read lock. This design ensures data consistency and visibility.
ReentrantLock uses the volatile variable state as a flag bit to determine whether the lock is occupied or reentrant, while ReentrantWriteReadLock uses this variable “bitwise cut” for both read and write locks. Read/write lock variables are divided into two parts, the high 16 bits represent read, the low 16 bits represent write, so the state and times of read lock and write lock are judged by bit operation.
The write lock is an exclusive lock that supports reentrant, while the read lock is a shared lock that supports reentrant and can be acquired by multiple threads simultaneously. For read locks, the number of times each thread acquires a read lock is stored in a ThreadLocal and maintained by the thread itself.
Read-write locks support the lock degradation function. Lock degradation means that a write lock is degraded to a read lock. It means that when a thread has a write lock, it acquires the read lock and then releases the write lock. This design can ensure the data visibility, so if the thread does not get read lock but a direct release the write lock, while another thread to obtain the write lock and modify the data, then the current thread can’t perceive other threads to modify data, when this thread once again read lock, access to the data may not be the latest data. Lock degradation ensures the visibility of data.
Condition interfaces
In Java, any Object has a set of monitor methods, including WAIT, notify, and notifyAll. These methods cooperate with synchronized to realize the communication between threads. The Condition interface also provides object-like monitor methods. With Lock to achieve thread communication.
The Condition interrupt methods include await, signal, and signalAll. To call these methods, the thread needs to obtain the Lock associated with the Condition object, which is created by the Lock object, that is, Condition depends on the Lock object.
`Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); `
When a thread calls the await method, the thread releases the lock and waits until another thread calls the Condition’s signal method, notifying the current thread, and the current thread returns from the await method having acquired the lock.
The implementation of Condition depends on AQS. As mentioned above, the operation of Condition needs to obtain the associated lock, so the implementation of Condition is realized as an internal class in AQS. Meanwhile, each Condition object is await queue. The wait queue is a FIFO queue. Each node in the queue contains a reference to a thread, which is the thread waiting on the Condition object (the thread calling the await method). The thread then releases the lock and constructs the thread as a node to join the wait queue.
The wait queue has head node and tail node. When a node joins the wait queue, it only needs to point the tail node to the node. Moreover, it can be found that the operation joining the wait queue does not use CAS to ensure thread safety, because the thread calling the await method must be the thread that has obtained the lock. That is, the thread safety of waiting queues is guaranteed by locks.
In addition, a Lock object can have multiple Condition objects, that is, it can have multiple wait queues. From the previous summary, a Lock object can have a synchronization queue and multiple wait queues.
The synchronization queue and the wait queue can be associated until, when a thread calls the await method, it is equivalent to moving from the start of the synchronization queue to the end of await queue.
When the signal method is called, the longest waiting node in the wait queue (the first node) is woken up and moved to the synchronous queue before being woken up.
Much of this content comes from the book The Art of Concurrent Programming in Java.