Synchronization control of java thread--reentrant lock ReentrantLock

 Our commonly used synchronized keyword is one of the simplest thread synchronization control methods, which determines whether a thread can access critical section resources. At the same time Object.wait() and Object.notify() methods play the role of thread waiting and notification. These tools play an important role in implementing complex multithreaded cooperation.
    Here, we introduce an alternative to synchronized, Object.wait() and Object.notify() methods - reentrant locks. Let's first look at an example of a reentrant lock:
 
    public ReentrantLock lock = new ReentrantLock();//Declare a reentrant lock
     ......
     lock.lock();//Start locking
     doSomething();//Code block that needs to be synchronized
     lock.unlock();//Release the lock
 
      As can be seen from the above code, compared with synchronized, reentrant locks have an explicit operation process, and developers need to manually lock and release the lock. Therefore, the flexibility of reentrant locks for logical control is much higher than that of synchronized. However, when exiting the critical section, you must remember to release the lock, otherwise other threads will no longer be able to access the critical section resources.
 
      The reason why it is called a reentrant lock is that this kind of lock can be entered repeatedly (of course only limited to the same thread). For example, the following form is also possible:
 
     lock.lock();//Start locking
     lock.lock();
     doSomething();//Code block that needs to be synchronized
     lock.unlock();//Release the lock
     lock.unlock();
 
    It is allowed for a thread to acquire the same lock multiple times in a row. Otherwise, the same thread will deadlock with itself when it acquires the lock for the second time. However, it should be noted that if the same thread acquires the lock multiple times, it must release the same number of locks when releasing the lock. If there is less release, other threads cannot enter the critical section. If there is too much release, an IllegalMonitorStateException will be thrown.
 
     In addition to the flexibility of use, reentrant locks also provide some advanced functions, such as interrupt response, time-limited waiting, and fair locks.
 
     Interrupt response:
    For synchronized, if a thread is waiting for a lock, it either acquires the lock to perform its task, or it keeps waiting for the lock. With reentrant locks, there is another option, that is, the thread can be interrupted, that is, while the thread is waiting for the lock, the program can cancel the request for the lock as needed. Interrupts provide this mechanism, so that when the program is waiting for a lock, it can still receive notifications and be told that there is no need to continue waiting. This situation is very helpful for dealing with deadlocks.
    The following simulates a deadlock and resolves this deadlock by interrupting:
 
     //declare two reentrant locks
    public ReentrantLock lock1 = new ReentrantLock();
    public ReentrantLock lock2 = new ReentrantLock();
 
    class MyThread implements Runnable(){
          private int lock;
          public MyThread (int lock){
              this.lock = lock;
          }
          @Override
          public void run(){
              if(lock == 1){
                   lock1.lockInterruptibly();//Apply for lock 1. If you want to use the interrupt function, you need to use lockInterruptibly() to apply for a lock
                   Thread.sleep(5000);
                    lock2.lockInterruptibly();//Apply for lock 2
              }else{
                    lock2.lockInterruptibly();//Apply for lock 2
                   Thread.sleep(5000);
                    lock1.lockInterruptibly();//Apply for lock 1
              }
              
              lock1.unlock();//Release the lock
              lock2.unlock();
          }
     }
 
     public static void main(String[] s){
          //t1 requests lock 1 first, then lock 2
          //t2 requests lock 2 first, then lock 1
          //In this way, the two threads will wait for each other, causing a deadlock
          Thread t1 = new Thread(new MyThread(1));
          Thread t2 = new Thread(new MyThread(2));
          t1.start();
          t2.start();
          //The above two threads have formed a deadlock
          Thread.sleep(1000);
          t2.interrupt();//Interrupt T2, give up the request for the lock, and release the lock held
    }
 
     Wait for a limited time:
    In addition to waiting for external notifications, another way to avoid deadlocks is to wait for a limited time. Given a waiting time, if the thread has not acquired the lock after this time, it will give up waiting. We can use the tryLock() method to wait for a limited time. For example, lock.tryLock(5, TimeUnit.SECONDS) means setting a waiting time of 5 seconds. If the lock has not been obtained after 5s, it will return false.
 
    if(lock.tryLock(5,TimeUnit.SECONDS)){
          doSomething();
    }else{
          System.out.println("The lock was not acquired, go away.");
    }
 
    The tryLock() method can also be run without parameters. In this case, the thread will request the lock. If the request is not available, it will return false immediately without waiting and no deadlock will occur.
 
    Fair lock:
      In most cases, the application of the lock is unfair, that is, the thread that waits first does not necessarily obtain the lock first, which is unfair to the thread waiting for the lock. For fair locks, locks are allocated in a first-come-first-served order. A major feature of fair locks is that they do not produce starvation. As long as you queue, you can eventually wait for resources.
    If we use the synchronized keyword, the resulting lock is unfair, and reentrant locks allow us to set their fairness, which provides a constructor:
    public ReentrantLock(boolean fair)
    When fair is true, it means the lock is fair. Although fair locks are beautiful, to achieve fair locks, an ordered queue must be maintained. Therefore, fair locks have high implementation costs and relatively low performance. Therefore, by default, locks are unfair.
 
     Summarize:
    ReentrantLock has several important methods:
    1. lock(): acquire the lock, wait if the lock is occupied
    2. lockInterruptibly(): Acquire the lock, but respond to the interrupt first
    3. tryLock(): try to acquire the lock, and set the waiting time, the timeout returns false
    4. unlock(): release the lock
 
 
    References:
    "java high concurrent programming"

Guess you like

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