ReentrantLock
ReentrantLock is a ReentrantLock, which means that a thread can repeatedly lock a resource. ReentrantLock is similar to synchronized in that it can ensure thread safety, but provides a more powerful and flexible mechanism than synchronized, such as interruptible lock acquisition and timed lock acquisition.
In addition, ReentrantLock also provides a fair lock and an unfair lock. The difference between the two is whether the lock is acquired in the same order as the lock request. When selecting a fair lock, the thread with the longest waiting time will acquire the lock first, but the efficiency of obtaining a fair lock is usually lower than that of obtaining an unfair lock. You can specify fair or unfair selection in the constructor by passing arguments.
Fair lock
In ReentrantLock, there is an abstract inner class Sync that inherits from AQS, delegating most of ReentrantLock’s functionality to Sync, and internally defining the Lock () abstract method, The nonfairTryAcquire() method is implemented by default, which is the default implementation of unfair locks.
Sync has two subclasses, fairlock FairSync and NonFairSync, which implement the Lock () method in Sync and tryAcquire() method in AQS.
NonFairSync
The lock() method in NonFairSync is implemented as follows:
final void lock(a) {
if (compareAndSetState(0.1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
Copy the code
First, an unfair lock can immediately attempt to acquire the lock, and if it fails, the acquire method in AQS is called, which in turn calls the tryAcquire method implemented by the custom component:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
Copy the code
The nonfairTryAcquire() method is implemented by default in Sync:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// Use CAS to set the synchronization status
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true; }}else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // Integer overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
Copy the code
In this case, the state of the current thread is determined to be 0, that is, whether the lock is idle. If so, an attempt is made to acquire the lock, and the current thread is set as the thread holding the lock successfully.
Otherwise, the current thread is determined to be the thread that holds the lock. If so, the lock is acquired by increasing the synchronization status value, which verifies that the lock is reentrant. After acquiring the lock, you can continue to acquire the lock by simply increasing the synchronization status value.
FairSync
The implementation of the lock() method in FairSync is as follows:
final void lock(a) {
acquire(1);
}
Copy the code
A fair lock can only call AQS acquire() and then tryAcquire() implemented by a custom component:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// Whether there is a precursor node
if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true; }}else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
Copy the code
The only difference here with an unfair lock is that the HasqueuedToraise method, used to determine whether there is a precursor node in the synchronous queue, is called upon obtaining the synchronous state. That is, the current thread can only attempt to acquire the lock if there is no other thread in front of it.
Release the lock
ReentrantLock’s unlock method internally calls AQS ‘release method to release the lock, which in turn calls the custom component implementation’s tryRelease method:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// The current thread is not a thread holding the lock and cannot be released
if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
Copy the code
First, determine if the current thread is the thread that holds the lock, and if not, raise an exception. If so, subtract the synchronization status value to determine whether the synchronization status is 0, that is, the lock is fully released and other threads can acquire the synchronization status.
If not, the synchronization status value is only set using the setState method.
Designated fairness
Fairness can be specified in the constructor of ReentrantLock:
- An unfair lock is created by default
public ReentrantLock(a) {
sync = new NonfairSync();
}
Copy the code
- Create a lock that specifies fairness.
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Synchronized and ReentrantLock
Here’s a summary of the similarities and differences between synchronized and ReentrantLock:
- Can be used to achieve synchronous access between threads;
- Both are reentrant locks, meaning that a thread can repeatedly lock a resource.
The differences are as follows:
- The synchronization mechanism is different:
synchronized
throughJava
Object dependentMonitor
Monitor implementation (not considering biased locking, lightweight locking);ReentrantLock
throughCAS
,AQS
和LockSupport
Etc.;
- Visibility implementation mechanisms are different:
synchronized
Rely onJVM
The memory model guarantees the visibility of multithreaded memory containing shared variables.ReentrantLock
throughASQ
中volatile
The type ofstate
The synchronization status value ensures the visibility of multithreaded memory containing shared variables.
- Different ways of use:
synchronized
Can be used to modify instance methods (locking instance objects), static methods (locking class objects), and synchronized code blocks (specified locking objects).ReentrantLock
You need to call it explicitlylock
Method, and infinally
Block release.
- Different levels of function richness:
synchronized
Only the simplest locking is provided.ReentrantLock
Provides timed lock acquisition, interruptible lock acquisition,Condition
(provideawait
,signal
And so on.
- Different lock types:
synchronized
Only unfair locks are supported.ReentrantLock
Provides fair and unfair lock implementations. But unfair lock is more efficient than fair lock.
Before synchronized optimization, it was heavyweight and performed much worse than ReentrantLock, but since synchronized introduced techniques such as biased locking, lightweight locking (spin locking), lock elimination, lock coarsing, and so on, the performance of the two has become similar.
In general, use ReentrantLock only if you need to use the other features ReentrantLock provides, such as interruptible, timed, pollable, fair lock acquisition, etc. Otherwise use synchronized, which is simple and convenient.