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:
    • synchronizedthroughJavaObject dependentMonitorMonitor implementation (not considering biased locking, lightweight locking);
    • ReentrantLockthroughCAS,AQSLockSupportEtc.;
  • Visibility implementation mechanisms are different:
    • synchronizedRely onJVMThe memory model guarantees the visibility of multithreaded memory containing shared variables.
    • ReentrantLockthroughASQvolatileThe type ofstateThe synchronization status value ensures the visibility of multithreaded memory containing shared variables.
  • Different ways of use:
    • synchronizedCan be used to modify instance methods (locking instance objects), static methods (locking class objects), and synchronized code blocks (specified locking objects).
    • ReentrantLockYou need to call it explicitlylockMethod, and infinallyBlock release.
  • Different levels of function richness:
    • synchronizedOnly the simplest locking is provided.
    • ReentrantLockProvides timed lock acquisition, interruptible lock acquisition,Condition(provideawait,signalAnd so on.
  • Different lock types:
    • synchronizedOnly unfair locks are supported.
    • ReentrantLockProvides 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.