Java

Talking about the Lock in Java: How Powerful is ReentrantLock?

Published Time : 2025-11-28

When you encounter multithreading issues while writing code, do your first reaction be to simply add synchronization? To be honest, this trick is indeed sufficient in most cases. But when you really encounter complex concurrent scenarios, you will find that synchronization is a bit inadequate.

Today, let's delve into Java and the more powerful lock in the package - ReentrantLock.

First, let's talk about what it really is

Simply put, ReentrantLock is a reentrant mutex lock provided after Java 5. What is reentrant? That is to say, the same thread can obtain the same lock multiple times without locking itself.

Imagine this scenario: you call another method that also requires the same lock within a locked method. If it can't be re entered, then it's over, the thread is stuck in its own hands. But ReentrantLock is very smart, it remembers who took the lock, and if it's one of its own people, it can safely let you in.

How does the bottom layer operate?

When it comes to principles, we have to mention AQS (AbstractQueuedSynchronizer), which can be said to be the soul of Java and packet delivery. All capabilities of ReentrantLock are built upon the foundation of AQS.

AQS maintains a state variable 'state' and a waiting queue internally. A state of 0 indicates that the lock is unoccupied, while a value greater than 0 indicates it is occupied, and the value precisely records the number of times the lock has been locked. The waiting queue is used to manage threads that did not grab the lock and make them queue obediently.

CAS (Compare And Swap) is used here, which is an atomic operation that ensures that only one thread can successfully modify it at a time. Threads that fail to grab the lock will not foolishly idle, but will be suspended and awakened when the lock is released.

Fair or unfair, that's the question

ReentrantLock has an interesting feature: it supports both fair and unfair modes.

Unfair locks are like squeezing on a bus, whoever has the strength gets on first, regardless of who comes first. When the thread arrives, try to grab the lock directly. If you can't get it, queue up again. This method has high throughput, but it may cause the latecomer to get first served, which is thread starvation.

Fair lock is like queuing up to buy tickets, emphasizing first come, first served. The new thread will first check if anyone is waiting in the queue, and if so, obediently go to the end of the queue to queue.

So how should we choose in actual development? If the thread in your scenario holds the lock for a short period of time or has high throughput requirements, using a non fair lock usually works better. But if you are worried that some threads may not be able to grab the lock for a long time, then you need to use fair locks to ensure fairness.

Let's see what it can do in actual projects

Bank transfer: wisdom to avoid deadlocks

The biggest fear of the financial system is deadlock. Imagine two accounts transferring money to each other. If not handled properly, thread A locks account 1 and account 2, and thread B locks account 2 and account 1, resulting in a deadlock.

Here, tryLock is used instead of directly locking, and a timeout is set so that even if a deadlock does occur, it will not wait indefinitely.

Cache System: The Clever Use of Conditional Variables

When doing caching, we often need to control the size of the cache. When the cache is full, the write thread needs to wait; When the cache is empty, the read thread needs to wait. This scenario can also be done using synchronized with wait/notify, but the code is awkward to write.

Using ReentrantLock's conditional variables is much more elegant:

//img.enjoy4fun.com/news_icon/d3thh88fe6kc72spng7g.png

Please note that 'while' is used instead of 'if' to check the condition, as the condition may change again after the thread is awakened and needs to be rechecked.


Game Server: Re entrant Value

In game servers, it is often necessary to perform various operations on player objects. For example, if a player receives damage and their health decreases, if their health drops to 0, it triggers a decease logic.

//img.enjoy4fun.com/news_icon/d3thh68fe6kc72spne9g.png

This reentrant feature makes our code much more natural to write, without constantly worrying about nested locks.

Several tips in use

It must be unlocked in the final

This may seem simple, but it's easy to forget. I have had a painful experience where the lock was not released due to an abnormality and the entire system froze.

TryLock is more friendly than lock

Especially in scenarios where waiting may take a long time, tryLock can set a timeout to prevent threads from waiting indefinitely.

Fair Lock is not the default choice

Unless there is a mandatory order requirement, use an unfair lock first. In most cases, the performance of unfair locks is much better.

Conditional variables need to be checked using a while loop

Remember this pattern: while (! Condition) {condition. await();}


Finally, let me say a few words

To be honest, in regular development, synchronization can indeed meet 80% of the requirements. But when you need finer grained control, such as interruptible lock retrieval, timeout mechanisms, and multiple conditional variables, the advantages of ReentrantLock become apparent.

However, it should also be noted that the greater the ability, the greater the responsibility. ReentrantLock is more complex to use than synchronized and requires manual locking and unlocking. If you forget to unlock it, the problem can be significant.

So my suggestion is to use synchronized first, and when it's really not enough, then use ReentrantLock as a tool. After all, using the right tools in the right scenarios is the best programming practice.