ReentrantLock (reentrant lock)

Do you understand ReentrantLock? Is it a fair lock?

ReentrantLock (reentrant lock) implements the Lock interface and is a reentrant and exclusive lock, and synchronized keyword is similar, but ReentrantLock is more flexible and powerful, adding advanced functions such as polling, timeout, interrupt, fair lock and unfair lock.

Reentrancy means that in the same thread, after the external method obtains the lock, the inner recursive method can still obtain the lock. If the lock is not reentrant, then when the same thread obtains the lock twice A deadlock will occur.

Exclusive lock means that the lock can only be acquired by one thread at the same time, and other threads acquiring the lock can only wait in the synchronization queue.

ReentrantLock uses unfair locks by default, and you can also explicitly specify fair locks through the constructor.

  • Fair lock: After the lock is released, the thread that applied first gets the lock first. The performance is poor. In order to ensure the absolute order in time, fair lock requires more frequent context switching.
  • Unfair lock: After the lock is released, the thread that applied later may obtain the lock first, randomly or in accordance with other priorities. The performance is better, but it may cause some threads to be unable to obtain the lock.

Similarities and differences between synchronized and ReentrantLock

  • Both are reentrant locks

  • synchronized depends on JVM and ReentrantLock depends on API. synchronized depends on JVM, and ReentrantLock is implemented at the JDK level, which is also the API level. It requires the lock() and unlock() methods to cooperate with the try/finally statement block. To be done.

  • synchronized does not require the user to manually release the lock, while ReentrantLock requires the user to manually release the lock.

  • ReentrantLock adds some advanced features than synchronized:

    Waiting for Interruptible: ReentrantLock provides a mechanism to interrupt threads waiting for locks. This mechanism is implemented through lock.lockInterruptibly(). That is to say, the waiting thread can choose to give up waiting and deal with other things instead.

    Achievable fair lock: ReentrantLock can specify whether it is a fair lock or an unfair lock. And synchronized can only be an unfair lock. The so-called fair lock means that the thread waiting first obtains the lock first. ReentrantLock is unfair by default. You can specify whether it is fair through the ReentrantLock(boolean fair) constructor of the ReentrantLock class.

    Selective notification can be implemented (locks can be bound to multiple conditions): synchronized keyword and wait() and notify() The combination of /notifyAll() methods can implement the waiting/notification mechanism. Of course, the ReentrantLock class can also be implemented, but it requires the help of the Condition interface and the newCondition() method.

How ReentrantLock avoids deadlock: responding to interrupts, pollable locks, and timed locks

(1) Respond to interrupts: In synchronized, if a thread tries to acquire a lock, the result is to either acquire the lock and continue execution, or continue to wait. ReentrantLock also provides the possibility to respond to interrupts, that is, while waiting for the lock, the thread can cancel the lock request as needed.

(2) Pollable lock: Obtain the lock through boolean tryLock(). If there is an available lock, acquire the lock and return true. If there is no available lock, return false immediately.

(3) Timing lock: Obtain the lock through boolean tryLock(long time, TimeUnit unit) throws InterruptedException. If an available lock is acquired within the specified time and the current thread is not interrupted, the lock is acquired and true is returned. If no available lock can be acquired within the specified time, the current thread will be disabled and will remain dormant until the following three situations occur.

  • The current thread acquires an available lock and returns true.
  • If the interrupt status of the current thread is set when it enters this method, or the current thread is interrupted when acquiring the lock, InterruptedException will be thrown and the interrupted status of the current thread will be cleared.
  • If the time for the current thread to acquire the lock exceeds the specified waiting time, false will be returned. If the set time is less than or equal to 0, the method will not wait at all.

Three methods for ReentrantLock to seize locks

  • The lock() method is used to block lock grabbing. The thread will remain blocked when the lock cannot be grabbed.
  • The tryLock() method is used to try to grab the lock. This method has a return value. If it succeeds, it returns true. If it fails (the lock has been acquired by another thread), it returns false. This method will return immediately no matter what. When the lock cannot be grabbed, the thread will not be blocked forever like calling the lock() method.
  • The tryLock(long time, TimeUnit unit) method is similar to the tryLock() method, except that this method will block for a period of time when the lock cannot be grabbed. If the lock is acquired during blocking, true will be returned immediately, and false will be returned upon timeout.

(1)Use the lock() method


 public void lock()

The template code is as follows:

 ReentrantLock lock = new ReentrantLock();
 lock.lock(); //1: Seize the lock
 try {
     //2: Successfully grab the lock and execute the critical section code
 } finally {
     lock.unlock();//3: Release the lock
 }

Notice:

  • The lock release operation lock.unlock() must be executed in the finally block of the try-catch structure, otherwise, if the critical section code throws an exception , the lock may never be released.

  • The preemptive lock operation lock.lock() must be outside the try statement block, not within the try statement block.

    Reason 1: The lock() method does not declare that it throws an exception, so it does not need to be included in the try block.

    Reason two: The lock() method may not be able to successfully seize the lock. If it does not succeed, of course there is no need to release the lock, and releasing the lock without owning the lock may cause the operation to run. abnormal.

  • Do not insert any code between the preemption lock operation lock.lock() and the try statement to avoid throwing an exception and causing the lock release operation lock.unlock() to fail to execute. , causing the lock to not be released.

(2) Call the tryLock() method to grab the lock non-blockingly

public boolean tryLock()

lock() is a blocking preemption. If the lock is not grabbed, the current thread will be blocked.

tryLock() is a non-blocking preemption. If the lock is not grabbed, the current thread will return immediately and will not be blocked.

//Create lock object
ReentrantLock lock = new ReentrantLock();
if(lock.tryLock()){//1: Try to seize the lock
    try {
        //2: Successfully grab the lock and execute the critical section code
    } finally {
        lock.unlock(); //3: Release the lock
    }
}else{
    //4: Failed to grab the lock, perform backup action
}

3) Call the tryLock(long time, TimeUnit unit) method to grab the lock

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

The tryLock(long timeout, TimeUnit unit) throws InterruptedException method is used to grab the lock within a limited time. This method will block and wait for a period of time when grabbing the lock. The time parameter represents The maximum blocking duration, the unit parameter is the unit of duration.

//Create lock object
ReentrantLock lock = new ReentrantLock();
try {
    if(lock.tryLock(1, TimeUnit.SECONDS)){//1: Try to seize the lock
        try {
            //2: Successfully grab the lock and execute the critical section code
        } finally {
            lock.unlock(); //3: Release the lock
        }
    }else{
        //4: Failed to grab the lock within a limited time, perform backup action
    }
} catch (InterruptedException e) {
   e.printStackTrace();
}
. 

Condition

Similar to the wait and notify methods of the Object object, based on the Lock explicit lock, JUC also provides a "wait-notify" method for inter-thread implementation. Interface java.util.concurrent.locks.Condition.

(1) Main methods of Lock interface

public interface Condition{
    //Method 1: Wait to add the current thread to the waiting queue and release the current lock
    //When other threads call signal(), a thread in the waiting queue will be awakened and grab the lock again.
    void await() throws InterruptedException;
    
    //Method 2: Notification. This method is functionally semantically equivalent to Object.notify()
    //Wake up a thread in the await() waiting queue
    void signal();
    
    //Method 3: Notify all. Wake up await() to wait for all threads in the queue. This method is semantically equivalent to Object.notifyAll()
    void signalAll();
    
    //Method 3: Wait for a limited time. This method is semantically equivalent to await()
    //The difference is that after the specified time waiting timeout, if it is not awakened, the thread will stop waiting.
    //The thread waits for timeout and returns false, otherwise it returns true
boolean await(long time, TimeUnit unit) throws InterruptedException
}

The signal (notification) method of the Condition object and the await (wait) method of the same object are used in one-to-one pairing, that is to say, The signal (or signalAll) method of a Condition object cannot wake up the await on other Condition objects. Thread.

The Condition object is based on explicit lock, so you cannot create a Condition object independently. Instead, you need to use an explicit lock instance to obtain its bound Condition. Object.

However, each Lock explicit lock instance can have any number of Condition objects. Specifically, you can use the lock.newCondition() method to obtain a Condition instance bound to the current explicit lock, and then use the Condition >The instance performs inter-thread communication in the "wait-notify" manner.

 public class ReentrantLockCondition {
     //Create an explicit lock
     static Lock lock=new ReentrantLock();
     //Get an explicit lock-bound Condition object
     static private Condition condition=lock.newCondition();
 ?
     //Wait for the thread to execute the asynchronous target task
     static class WaitTarget implements Runnable{
         @Override
         public void run() {
             lock.lock();//1: Seize the lock
             try {
                 System.out.println("I am the waiting party");
                 condition.await();//2: Start waiting and release the lock
                 System.out.println("Notification received, wait to continue execution");
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }finally {
                 lock.unlock();//Release the lock
             }
 ?
         }
     }
     //Notify the asynchronous target task of the thread
     static class NotifyTarget implements Runnable{
         @Override
         public void run() {
             lock.lock(); //3: Grab the lock
             try {
                 System.out.println("I am the notifying party");
                 condition.signal(); //4: Send notification
                 System.out.println("A notification was issued, but the thread has not released the lock immediately");
             } finally {
                 lock.unlock(); //5: After releasing the lock, the waiting thread can obtain the lock
             }
         }
     }
 ?
     public static void main(String[] args) throws InterruptedException {
         //Create waiting thread
         Thread waitThread = new Thread(new WaitTarget(), "WaitThread");
         //Start waiting thread
         waitThread.start();
         Thread.sleep(2000);//wait for a while
 ?
         //Create notification thread
         Thread notifyThread = new Thread(new NotifyTarget(), "NotifyThread");
         //Start notification thread
         notifyThread.start();
     }
 }

syntaxbug.com © 2021 All Rights Reserved.