Example code of ReentrantLock reentrant lock for Java concurrent programming

  • 2021-08-16 23:47:52
  • OfStack

Directory 1. ReentrantLock Reentrant Lock Overview 2. Reentrant 3. Interruptible 4. Lock Timeout 5. Fair Lock 6. Condition Variable Condition

1. ReentrantLock reentrant lock overview

Compared with synchronized, it has the following characteristics
Interruptible
The synchronized lock cannot be interrupted when added, the a thread applies the lock, and the b thread cannot cancel it
You can set a timeout
When synchronized goes to acquire the lock, if the other party holds the lock, it will enter entryList1 and wait straight. The reentrant lock can set a timeout time, and if the lock cannot be acquired within the specified time, the lock will be abandoned
Can be set to a fair lock
Prevent thread starvation, that is, first come, first served. If there are more people competing for it, it may happen that you will never get a lock

Supports multiple condition variables and multiple waitset (does not support condition 1 de-a does not support condition 2 de-b)
synchronized only supports the same waitset.
Like synchronized 1, both support reentrant

Basic grammar


//  Acquisition lock 
reentrantLock.lock();
try {
 //  Critical region 
} finally {
 //  Release lock 
 reentrantLock.unlock();
}

synchronized protects critical sections at the keyword level, while reentrantLock protects critical sections at the object level. The critical section is the code that accesses the shared resource. In finally, it is indicated that the lock will be released regardless of whether an exception occurs in the future, and releasing the lock calls the unlock method. Otherwise, the lock cannot be released, and other threads will never acquire the lock.

2. Reentrant

Reentrant means that if the same thread acquires the lock for the first time, it has the right to acquire the lock again because it is the owner of the lock
If it is a non-reentrant lock, you will be blocked by the lock when you get the lock for the second time
ReentrantLock and synchronized are both reentrant locks.


public class TestReentranLock1 {
 static ReentrantLock lock = new ReentrantLock();
 public static void main(String[] args) {
  method1();
 }
 public static void method1() {
  lock.lock();
  try {
   System.out.println("execute method1");
   method2();
  } finally {
   lock.unlock();
  }
 }
 public static void method2() {
  lock.lock();
  try {
   System.out.println("execute method2");
   method3();
  } finally {
   lock.unlock();
  }
 }
 public static void method3() {
  lock.lock();
  try {
   System.out.println("execute method3");
  } finally {
   lock.unlock();
  }
 }
}

execute method1
execute method2
execute method3

3. Interruptible

Interruptible means that while waiting for a lock, other threads can use the interrupt method to terminate my wait. The synchronized lock is non-interruptible.
If we want to be interrupted while waiting for a lock, we need to lock the lock object using the lockInterruptibly () method instead of the lock () method


public class TestReentranLock2 {
 public static void main(String[] args) {
  ReentrantLock lock = new ReentrantLock();
  Thread t1 = new Thread(() -> {
   try {
    // If there is no competition, this method gets the lock Lock of object 
    // If there is a race, it enters the blocking queue and waits, which can be used by other threads interrupt Interrupt 
    System.out.println(" Try to acquire a lock ");
    lock.lockInterruptibly();
   } catch (InterruptedException e) {
    e.printStackTrace();
    System.out.println(" Was interrupted in the process of waiting for the lock ");
    return;
   }
   try {
    System.out.println("t1 The lock was acquired ");
   } finally {
    lock.unlock();
   }
  }, "t1");
  lock.lock();
  System.out.println(" The main thread acquired the lock ");
  t1.start();
  try {
   try {
    sleep(1);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   t1.interrupt();
   System.out.println(" Execution interrupt t1");
  } finally {
   lock.unlock();
  }
 }
}

 The main thread acquired the lock 
 Try to acquire a lock 
 Execution interrupt t1
 Was interrupted in the process of waiting for the lock 
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at cn.yj.jvm.TestReentranLock2.lambda$main$0(TestReentranLock2.java:15)
	at java.lang.Thread.run(Thread.java:748)

Note that if it is non-interruptible mode, the wait will not be interrupted even if interrupt is used, that is, it is not. That is, the lock () method is used.
This method can avoid deadlock and endless waiting.


ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
 System.out.println(" Start ...");
 lock.lock();
 try {
  System.out.println(" The lock was acquired ");
 } finally {
  lock.unlock();
 }
}, "t1");
lock.lock();
System.out.println(" The lock was acquired ");
t1.start();
try {
 sleep(1);
 t1.interrupt();
 System.out.println(" Execution interrupt ");
 sleep(1);
} finally {
 System.out.println(" Lock released ");
 lock.unlock();
}

4. Lock timeout

ReentranLock supports interruptible, in fact, in order to avoid dead waiting, which can reduce the occurrence of deadlock. In fact, interruptible this way belongs to a kind of passive avoidance of death, which is interrupted by other threads interrupt.
Lock timeout is an active way to avoid waiting.
The lock is acquired using the tryLock () method, that is, it tries to acquire the lock, if it succeeds, it acquires the lock, and if it fails, it can wait without entering the blocking queue, and it will return false, indicating that it did not acquire the lock.

Fail at once


public static void main(String[] args) {
  ReentrantLock lock = new ReentrantLock();
  Thread t1 = new Thread(() -> {
   System.out.println(" Start ...");
   if (!lock.tryLock()) {
    System.out.println(" Unable to acquire the lock, fail immediately and return ");
    return;
   }
   try {
    System.out.println(" The lock was acquired ");
   } finally {
    lock.unlock();
   }
  }, "t1");
  lock.lock();
  System.out.println(" The lock was acquired ");
  t1.start();
  try {
   try {
    sleep(500);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } finally {
   lock.unlock();
  }
}

The lock was acquired
Start...
Unable to acquire the lock, fail immediately and return

Timeout failure
lock. tryLock (1, TimeUnit. SECONDS) indicates an attempt to wait for 1s. If the main thread does not release the lock, it returns false, and if it releases the lock, it returns true. tryLock also supports interruptions, which report exceptions.


ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
 log.debug(" Start ...");
 try {
  if (!lock.tryLock(1, TimeUnit.SECONDS)) {
   log.debug(" Get wait  1s  Failure after, return ");
   return;
  }
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 try {
  log.debug(" The lock was acquired ");
 } finally {
  lock.unlock();
 }
}, "t1");
lock.lock();
log.debug(" The lock was acquired ");
t1.start();
try {
 sleep(2);
} finally {
 lock.unlock();
}

Output

18:19:40. 537 [main] c. TestTimeout-Lock obtained
18:19:40. 544 [t1] c. TestTimeout-Start...
18:19:41. 547 [t1] c. TestTimeout-Failed after waiting for 1s and returned

5. Fairness Lock

For synchronized, it is an unfair lock. When one thread holds the lock, other threads will enter the blocking queue and wait. When the lock holder releases the lock, these threads will rush up. Whoever grabs it first will become the owner of monitor, instead of following the first-come, first-served rule.

ReentrantLock default is unfair
ReentrantLock has a parametric construction method. It is unfair to acquiesce.


 public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

We can guarantee its fairness by changing Boolean values to true. Threads in the impending blocking queue will be executed in the order of entering the blocking queue when competing for locks, on a first-come-first-served basis.

6. Conditional variable Condition

There are also conditional variables in synchronized, that is, the waitSet lounge when we talk about the principle. When the conditions are not met, enter waitSet and wait

The condition variable of ReentrantLock is stronger than that of synchronized in that it supports multiple condition variables, which is like

synchronized is a message that those threads that do not meet the conditions are all in a lounge and so on
ReentrantLock supports multiple lounges, including a lounge waiting for cigarettes and a lounge waiting for breakfast. When you wake up, you also wake up according to the lounge

Key points for use:

Need to acquire lock before await After await executes, it releases the lock and enters conditionObject to wait The thread of await is awakened (or interrupted, or timed out) to re-compete for lock lock After the lock lock is successfully competed, continue to execute after await signal is equivalent to notify, and signalAll is equivalent to notifyAll

static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
 new Thread(() -> {
  try {
   lock.lock();
   while (!hasCigrette) {
    try {
     waitCigaretteQueue.await();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   log.debug(" When it comes to its smoke, ");
  } finally {
   lock.unlock();
  }
 }).start();
 new Thread(() -> {
  try {
   lock.lock();
   while (!hasBreakfast) {
    try {
     waitbreakfastQueue.await();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   log.debug(" When it comes to its breakfast, ");
  } finally {
   lock.unlock();
  }
 }).start();
 sleep(1);
 sendBreakfast();
 sleep(1);
 sendCigarette();
}
private static void sendCigarette() {
 lock.lock();
 try {
  log.debug(" Here comes the smoke ");
  hasCigrette = true;
  waitCigaretteQueue.signal();
 } finally {
  lock.unlock();
 }
}
private static void sendBreakfast() {
 lock.lock();
 try {
  log.debug(" Here comes breakfast ");
  hasBreakfast = true;
  waitbreakfastQueue.signal();
 } finally {
  lock.unlock();
 }
}

Output

18:52:27. 680 [main] c. TestCondition-Here comes breakfast
18:52:27. 682 [Thread-1] c. TestCondition-Wait for its breakfast
18: 52: 28. 683 [main] c. TestCondition-Here comes the smoke
18:52: 28.683 [Thread-0] c. TestCondition-Wait for its smoke


Related articles: