Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use ReentrantLock Lock in Java concurrency

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "how to use ReentrantLock locks in Java concurrency". The content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to use ReentrantLock locks in Java concurrency".

The reentrant lock can replace the keyword synchronized.

In earlier versions of JDK5.0, reentrant locks performed much better than the keyword synchronized

But starting from JDK6.0, JDK has done a lot of optimization on the keyword synchronized, so that the performance gap between the two is not big.

The reentrant lock is implemented using ReentrantLock

1. The reentrant lock package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo implements Runnable {public static ReentrantLock lock = new ReentrantLock (); public static int i = 0; @ Override public void run () {for (int j = 0; j < 100000000; jacks +) {lock.lock (); lock.lock (); try {iBex + } finally {lock.unlock (); lock.unlock ();}} public static void main (String [] args) throws InterruptedException {ReentrantLockDemo tl = new ReentrantLockDemo (); Thread T1 = new Thread (tl); Thread T2 = new Thread (tl); t1.start (); t2.start () T1.join (); t2.join (); System.out.println (I);}}

Console print

20000000

Description

It is allowed for a thread to acquire the same lock twice in a row.

If this is not allowed, the same thread will have a deadlock with itself when it acquires the lock for the second time.

The program will be "stuck" in the process of applying for the lock for the second time.

It is important to note, however, that if the same thread acquires the lock multiple times, it must be released the same number of times when the lock is released.

If you release the lock more times, you will get a java.lang.IllegalMonitorStateException exception; conversely, if you release the lock less times, the thread still holds the lock, so other threads cannot enter the critical section.

2. Interrupt response

For the keyword synchronized, if a thread is waiting for a lock, there are only two cases, either it acquires the lock and continues to execute, or it waits.

Using reentrant locks provides another possibility that threads can be interrupted.

That is, while waiting for the lock, the program can cancel the request for the lock as needed.

Sometimes it is necessary to do so.

For example, if you have an appointment with your friend to play ball, if you wait for half an hour and your friend hasn't arrived, you suddenly receive a phone call saying that your friend can't come as promised due to an emergency, then you must be disappointed to go home.

Interrupts provide a similar mechanism.

If a thread is waiting for a lock, it can still receive a notification that it does not have to wait and can stop working.

This situation is helpful in dealing with deadlocks.

The following code creates a deadlock, but thanks to the lock break, we can easily resolve the deadlock.

Package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class IntLock implements Runnable {public static ReentrantLock lock1 = new ReentrantLock (); public static ReentrantLock lock2 = new ReentrantLock (); int lock; / * controls the locking order to facilitate the construction of deadlocks * * @ param lock * / public IntLock (int lock) {this.lock = lock } @ Override public void run () {try {if (lock = = 1) {lock1.lockInterruptibly (); try {Thread.sleep (500);} catch (InterruptedException e) {} lock2.lockInterruptibly () } else {lock2.lockInterruptibly (); try {Thread.sleep;} catch (InterruptedException e) {} lock1.lockInterruptibly ();}} catch (InterruptedException e) {e.printStackTrace () } finally {if (lock1.isHeldByCurrentThread ()) lock1.unlock (); if (lock2.isHeldByCurrentThread ()) lock2.unlock (); System.out.println (Thread.currentThread (). GetId () + ": thread exit");} public static void main (String [] args) throws InterruptedException {IntLock R1 = new IntLock (1) IntLock R2 = new IntLock (2); Thread T1 = new Thread (R1); Thread T2 = new Thread (R2); t1.start (); t2.start (); Thread.sleep (1000); / / interrupt one of the threads t2.interrupt ();}}

Console output

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 com.shockang.study.java.concurrent.lock.IntLock.run (IntLock.java:35)

At java.lang.Thread.run (Thread.java:748)

11: thread exit

12: thread exit

Description

After threads T1 and T2 start, T1 first occupies lock1, then lock2.

T2 first occupies lock2, and then requests lock1.

Therefore, it is easy to form mutual waiting between T1 and T2.

Here, the lockInterruptibly () method is used uniformly for lock requests.

This is a lock request action that can respond to an interrupt, that is, while waiting for a lock, it can respond to an interrupt.

At line 56 of the code, the main thread main is dormant, and the two threads are deadlocked.

In line 58 of the code, because the T2 thread is interrupted, T2 abandons the application for lock1 and releases the acquired lock2.

This operation causes the T1 thread to get the lock2 smoothly and continue to execute.

3. Time limit for waiting for lock application

In addition to waiting for external notification, another way to avoid deadlocks is to wait for a limited time.

Still take playing ball with a friend as an example. If a friend can't get back and he can't be reached, then after waiting for an hour or two, I think most people will spoil the fun and leave.

The same is true for threads.

Usually, we can't tell why a thread can't get the lock late.

Maybe it's because of the deadlock, maybe it's because of hunger.

If you give a waiting time and let the thread give up automatically, it makes sense for the system.

We can use the tryLock () method to do a time-limited wait.

TryLock (long, TimeUnit)

The following code shows the use of time-limited wait locks.

Package com.shockang.study.java.concurrent.lock;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class TimeLock implements Runnable {public static ReentrantLock lock = new ReentrantLock (); @ Override public void run () {try {if (lock.tryLock (5, TimeUnit.SECONDS)) {Thread.sleep (6000) } else {System.out.println ("get lock failed");}} catch (InterruptedException e) {e.printStackTrace ();} finally {lock.unlock ();}} public static void main (String [] args) {TimeLock tl = new TimeLock (); Thread T1 = new Thread (tl) Thread T2 = new Thread (tl); t1.start (); t2.start ();}}

Console print

Get lock failed

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException

At java.util.concurrent.locks.ReentrantLock$Sync.tryRelease (ReentrantLock.java:151)

At java.util.concurrent.locks.AbstractQueuedSynchronizer.release (AbstractQueuedSynchronizer.java:1261)

At java.util.concurrent.locks.ReentrantLock.unlock (ReentrantLock.java:457)

At com.shockang.study.java.concurrent.lock.TimeLock.run (TimeLock.java:20)

At java.lang.Thread.run (Thread.java:748)

Description

Here, the tryLock () method takes two parameters, one for the length of the wait and the other for the timing unit.

The unit here is set to seconds and the duration is 5, which means that the thread waits for up to 5 seconds in this lock request.

If the lock is not obtained for more than 5 seconds, false will be returned.

If the lock is successfully acquired, true is returned.

In this example, because the thread occupying the lock will hold the lock for up to 6 seconds, the other thread cannot acquire the lock within the wait time of 5 seconds, so the request for the lock will fail.

TryLock ()

The ReentrantLock.tryLock () method can also be run without arguments.

In this case, the current thread attempts to acquire the lock, and if the lock is not occupied by another thread, the lock application succeeds and immediately returns true.

If the lock is occupied by another thread, the current thread does not wait, but returns false immediately.

This mode does not cause threads to wait, so there is no deadlock.

Package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class TryLock implements Runnable {public static ReentrantLock lock1 = new ReentrantLock (); public static ReentrantLock lock2 = new ReentrantLock (); int lock; public TryLock (int lock) {this.lock = lock } @ Override public void run () {if (lock = = 1) {while (true) {if (lock1.tryLock ()) {try {try {Thread.sleep } catch (InterruptedException e) {} if (lock2.tryLock ()) {try {System.out.println (Thread.currentThread () .getId () + ": My Job done") Return;} finally {lock2.unlock ();}} finally {lock1.unlock () } else {while (true) {if (lock2.tryLock ()) {try {try {Thread.sleep } catch (InterruptedException e) {} if (lock1.tryLock ()) {try {System.out.println (Thread.currentThread () .getId () + ": My Job done") Return;} finally {lock1.unlock ();}} finally {lock2.unlock () } public static void main (String [] args) throws InterruptedException {TryLock R1 = new TryLock (1); TryLock R2 = new TryLock (2); Thread T1 = new Thread (R1); Thread T2 = new Thread (R2); t1.start (); t2.start ();}}

Console output

11:My Job done

12:My Job done

Description

The above code uses a locking order that is very easy to deadlock.

That is, first let T1 get lock1, then let 2 get lock2, and then make a reverse request, let T1 apply for lock2 and T2 apply for lock1.

In general, this causes T1 and 2 to wait for each other.

Wait, thus causing a deadlock.

However, this situation has greatly improved with the use of the tryLock () method.

Because the thread doesn't wait foolishly, it keeps trying, so as long as it executes long enough, the thread always gets all the resources it needs to execute normally (here, the thread acquires both lock1 and lock2 locks as a condition for it to execute normally).

After getting both lock1 and lock2, the thread prints out the message "My Job done" that marks the completion of the task.

4. Fair lock

In most cases, the lock application is unfair.

That is, thread 1 first requests lock A, and then thread 2 requests lock A.

So when lock An is available, can thread 1 or thread 2 acquire the lock?

This is not necessary, the system will just randomly pick one from the waiting queue for the lock.

Therefore, its fairness can not be guaranteed.

This is like buying a ticket without queuing up, everyone is gathered around the ticket window, and the conductor is so busy that he can't care who comes first and then casually find someone to issue the ticket.

On the other hand, this is not the case with a fair lock. According to the order of time, it will ensure that those who arrive first will get it first and those who arrive later will get it later.

A major feature of the fair lock is that it does not produce hunger.

Please refer to my blog about thread hunger-what does deadlock, live lock and hunger mean?

As long as you wait in line, you can finally wait for resources.

If we use the synchronized keyword for lock control, the resulting lock is unfair.

The reentrant lock allows us to set its fairness.

Its constructor is as follows:

/ * create an instance of ReentrantLock using the given fairness policy. * * @ param fair if this lock should use a fair sorting policy as true * / public ReentrantLock (boolean fair) {sync = fair? New FairSync (): new NonfairSync ();}

When the parameter fair is true, the lock is fair.

Fair lock looks beautiful, but to achieve fair lock must require the system to maintain an orderly queue, so the cost of fair lock is relatively high, but the performance is very low. Therefore, by default, the lock is unfair.

If there are no special requirements, there is no need to use fair locks.

Fair locks and unfair locks are also very different in thread scheduling performance.

The following code highlights the characteristics of fair locks.

Package com.shockang.study.java.concurrent.lock;import java.util.concurrent.locks.ReentrantLock;public class FairLock implements Runnable {public static ReentrantLock fairLock = new ReentrantLock (true); @ Override public void run () {while (true) {try {fairLock.lock (); System.out.println (Thread.currentThread (). GetName () + "acquire lock") } finally {fairLock.unlock ();} public static void main (String [] args) throws InterruptedException {FairLock R1 = new FairLock (); Thread T1 = new Thread (R1, "Thread_t1"); Thread T2 = new Thread (R1, "Thread_t2"); t1.start (); t2.start ();}}

Console output

Get the lock

Thread_t2 acquires lock

Thread_t2 acquires lock

Thread_t2 acquires lock

Thread_t2 acquires lock

Thread_t1 acquires lock

Thread_t1 acquires lock

Thread_t2 acquires lock

Thread_t2 acquires lock

Thread_t2 acquires lock

Thread_t1 acquires lock

Thread_t1 acquires lock

# omit

Description

Since the code produces a large amount of output, only part of it is intercepted here for explanation.

In this output, it is obvious that the two threads basically acquire the lock alternately, and it is almost impossible for the same thread to acquire the lock multiple times in a row, thus ensuring fairness.

If false is set, a thread will tend to acquire locks that are already held again, depending on the scheduling of the system. This allocation is efficient, but not fair.

Source code (JDK8) / * A reentrant mutex that has the same basic behavior and semantics as an implicit monitor lock (that is, synchronized) accessed by synchronous methods and statements, but with extended functionality. * * the reentrant lock belongs to the thread that last successfully locked but has not yet unlocked it. * * when the lock does not belong to another thread, the thread that called the lock returns and successfully acquires the lock. * * if the current thread already has a lock, the method returns immediately. This can be checked using the isHeldByCurrentThread and getHoldCount methods. * * the constructor of this class accepts optional fairness parameters. * * when set to true, the lock helps to grant access to the thread with the longest waiting time in the competitive state. Otherwise, this lock does not guarantee any specific access order. * * programs that use fair locks accessed by multiple threads may show lower overall throughput * * (that is, slower; usually much slower than those using the default settings, but there is a small difference in the time to acquire locks and guarantee non-hunger. * * Please note that the fairness of locks does not guarantee the fairness of thread scheduling. * * therefore, one of the multiple threads that use the fair lock can acquire the fair lock several times in a row, while other active threads do not and currently do not hold the lock. * * also note that the untimed tryLock () method does not support fairness settings. * * if the lock is available, it will succeed even if other threads are waiting. * * the recommended practice is to always use try block locking immediately after the call, most typically before / after the build, for example: * * class X {* private final ReentrantLock lock = new ReentrantLock (); * /. * * public void m () {* lock.lock (); / / block until condition holds * try {* / /. Method body *} finally {* lock.unlock () *}} * * in addition to implementing the lock interface, this class defines a number of public and protected methods to check the state of the lock. * * some of these methods are only useful for instrumentation and monitoring. * * the serialization of this class behaves the same as the built-in lock: the deserialized lock is unlocked, regardless of the state at the time of serialization. * * this lock supports up to 2147483647 recursive locks of the same thread. An attempt to exceed this limit results in an error thrown by the locking method. * * @ since 1.5 * @ author Doug Lea * / public class ReentrantLock implements Lock, java.io.Serializable thank you for your reading. The above is the content of "how to use ReentrantLock locks in Java concurrency". After the study of this article, I believe you have a deeper understanding of how to use ReentrantLock locks in Java concurrency, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report