Java read-write lock reentrant lock

Reentrant locks Reentrant locks
in Java (ie ReentrantLock), like JVM built-in locks (ie synchronized), are an exclusive lock.

ReentrantLock provides a variety of synchronization, such as time-limited synchronization ( timed lock ), which can be synchronized by Interrupt, that is, interrupted lock (synchronized synchronization cannot be Interrupt), etc.


In the case where the resource competition is not very fierce, the performance of Synchronized is better than that of ReetrantLock.

However, in the case of intense resource competition , the performance of Synchronized will drop dozens of times , but the performance of ReetrantLock can remain normal ;

Atomic atomic operation, the performance is slightly worse than synchronized in less severe cases, and can maintain the normal state when it is intense.

When it is intense, the performance of Atomic will be about twice that of ReentrantLock.

But it has a disadvantage, that is, only one value can be synchronized, and only one Atomic variable can appear in a piece of code, and more than one synchronization is invalid. Because he can't sync between multiple Atomics.

Therefore, when we write synchronization, we give priority to synchronized , and if there are special needs, we will further optimize it. If ReentrantLock and Atomic are not used properly, they will not only fail to improve performance, but may also bring disaster.

ReentrantLock acquires the lock in three ways:
    a) lock(), if the lock is acquired, it will return immediately, if another thread holds the lock, the current thread will remain dormant until the lock is acquired

    b) tryLock(), if the lock is acquired, it returns true immediately, and if another thread is holding the lock, it returns false immediately;

    c) tryLock(long timeout, TimeUnit  unit), if the lock is acquired, it will return true immediately. If another thread is holding the lock, it will wait for the time given by the parameter. During the waiting process, if the lock is acquired, it will return true , if the wait times out, return false;

    d) lockInterruptibly: if the lock is acquired, return immediately, if the lock is not acquired, the current thread is in a sleep state until the lock is acquired, or the current thread is interrupted by another thread

Reentrant locks can be defined as fair locks or unfair locks, and the default implementation is unfair locks .

A fair lock means that when multiple threads are blocked from acquiring the lock, the thread that first applies for the lock acquires the lock when the lock becomes available .

In a concurrent environment, each thread will first check the waiting queue maintained by the lock when acquiring a lock . If it is empty, or the current thread thread is the first in the waiting queue, it will occupy the lock, otherwise it will be added to the waiting queue. , and will retrieve itself from the queue according to the FIFO rules in the future

A fair lock can be constructed by passing true in the constructor of the reentrant lock (RenentrantLock), such as Lock lock = new RenentrantLock( true )


Unfair lock means that when multiple threads are waiting for the lock, when the lock becomes available, which thread obtains the lock is random .

synchonized is equivalent to an unfair lock. Unfair locks can be constructed by passing false in the constructor of the reentrant lock or by using the no-argument constructor

Read- write locks
guarantee atomicity and visibility.

Atomicity is more for write operations . For scenarios with more reads and fewer writes, a read operation does not need to block other read operations, but only needs to ensure that read and write or write and write do not occur at the same time.

At this time, if you use reentrant locks (ie exclusive locks), it will have a greater impact on performance. The read-write lock (ReadWriteLock) in Java is created for this scenario of reading more and writing less.

In fact, the ReadWriteLock interface is not inherited from the Lock interface, and ReentrantReadWriteLock only implements the ReadWriteLock interface but not the Lock interface.

ReadLock and WriteLock are static inner classes of the ReentrantReadWriteLock class, and they implement the Lock interface.


A ReentrantReadWriteLock instance contains a ReentrantReadWriteLock.ReadLock instance and a ReentrantReadWriteLock.WriteLock instance.

The read lock instance and write lock instance can be obtained respectively through the readLock() and writeLock() methods, and the corresponding lock can be obtained through the lock acquisition method provided by the Lock interface.


The locking rules for read-write locks are as follows:
After obtaining the read lock, other threads can obtain the read lock but cannot obtain the write lock
. After obtaining the write lock, other threads cannot obtain the read lock or the write lock.

Conditional lock
Conditional lock is just a concept to help users understand, in fact, there is no such lock as conditional lock.

For each reentrant lock, several condition objects can be bound by the newCondition() method.

 A reentrant lock can create several condition objects, and the signal() and signalAll() methods can only wake up waiting for the same condition object.
A reentrant lock can generate multiple condition variables, and different threads can wait for different conditions, enabling more fine-grained inter-thread communication.

Condition is an interface, and the basic methods are await() and signal() methods;

Condition depends on the Lock interface. The basic code for generating a Condition is lock.newCondition()
to call the await() and signal() methods of Condition, which must be protected by lock, that is, must be in lock.lock() and lock. Can only be used between unlocks

await() in Conditon corresponds to wait() of Object;
signal() in Condition corresponds to notify() of Object;
signalAll() in Condition corresponds to notifyAll() of Object.

 

principle

The premise of ReentrantLock implementation is AbstractQueuedSynchronizer, abstract queue synchronizer, referred to as AQS, which is the core of java.util.concurrent.

There is an abstract class Sync in ReentrantLock that inherits AQS.

 

We know that the essence of Lock is AQS. The queue maintained by AQS is the queue currently waiting for resources. After AQS is released, it will wake up all nodes in the queue from front to back in turn, so that their corresponding threads can resume execution until the queue is empty. .

Condition itself also maintains a queue, the role of the queue is to maintain a queue waiting for the signal signal.

However, the roles of the two queues are different. In fact, each thread only exists in one of the above two queues at the same time. The process is as follows:

1. When thread 1 calls reentrantLock.lock, it tries to acquire the lock. If successful, it returns and removes the thread from the AQS queue; otherwise, it blocks and remains in the AQS waiting queue.
2. When thread 1 calls the await method and is called, the corresponding operation is added to the waiting queue of the Condition, waiting for the signal signal; at the same time, the lock is released.
3. After the lock is released, the head node in the AQS queue will be awakened, so thread 2 will acquire the lock.
4. Thread 2 calls the signal method. At this time, there is only one node of thread 1 in the waiting queue of Condition, so it is taken out and added to the waiting queue of AQS. Note that at this time, thread 1 is not awakened, but is only added to the AQS waiting queue.
5. After the signal method is executed, thread 2 calls the unLock() method to release the lock. At this time, because there is only thread 1 in AQS, thread 1 is awakened and thread 1 resumes execution.


So:
sending the signal signal just adds the thread in the Condition queue to the waiting queue of AQS.

These threads will not be woken up until the thread that sends the signal calls reentrantLock.unlock() to release the lock.

It can be seen that the entire cooperation process is realized by the node moving back and forth in the waiting queue of AQS and the waiting queue of Condition.

As a condition class, Condition maintains a queue waiting for signals by itself, and adds nodes to the waiting queue of AQS at the right time to realize the wake-up operation.

 

signal is to wake up the first non-CANCELLED node thread in the Condition queue,

And signalAll is to wake up all non-CANCELLED node threads. The essence is to take one node out of the Condition queue or put all nodes into the AQS waiting queue.

Although all Nodes may be awakened, it is important to know that only one thread can acquire the lock, and other threads that have not acquired the lock still need to spin and wait, and go to step 4 (acquireQueued) mentioned above.

 


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325984575&siteId=291194637