ReentrantLock is a reentrant mutex, also known as exclusive lock.
As the name implies, ReentrantLock locks can only be held by one thread at the same time. Reentrant means that ReentrantLock locks can be acquired multiple times by the same thread.
ReentrantLock is divided into fair lock“ and unfair lock. The difference is whether the lock acquisition mechanism is fair. Lock is used to protect competing resources and prevent multiple threads from simultaneously operating on the same resources. ReentrantLock can only be acquired by one thread at the same time (when a thread acquires a lock, other threads must wait). It is through a FIFO waiting queue to manage all threads that try to acquire the lock. Under the fair lock mechanism, threads queue up to acquire locks in turn, while unfair locks acquire locks regardless of whether they are at the beginning of the queue when the locks are available.
// Create a ReentrantLock. By default it's a unfair lock ReentrantLock()
// Create a ReentrantLock. fair is true means it's a fair lock, fair is false means it's a unfair lock ReentrantLock(boolean fair)
// Query the hold count of lock of the current thread intgetHoldCount() // Return the current owner of the lock. If the lock is not owned, it will return null protected Thread getOwner() // Return a collection of thread that's waiting for the lock protected Collection<Thread> getQueuedThreads() // Return the numbber of thread that's waiting for the lock intgetQueueLength() // Returns a collection containing those threads that may be waiting on the given condition associated with this lock. protected Collection<Thread> getWaitingThreads(Condition condition) // Returns an estimate of the number of threads waiting on the given condition associated with this lock. intgetWaitQueueLength(Condition condition) // Queries whether any threads are waiting to acquire this lock. booleanhasQueuedThread(Thread thread) // Queries whether any threads are waiting to acquire this lock. booleanhasQueuedThreads() // Queries whether any threads are waiting on the given condition associated with this lock. booleanhasWaiters(Condition condition) // Returns true if this lock is fair. booleanisFair() // Return true if current thread holds this lock and false otherwise booleanisHeldByCurrentThread() // Queries if this lock is held by any thread. booleanisLocked() // Acquire the lock voidlock() // Acquires the lock unless the current thread is interrupted voidlockInterruptibly() // Returns a Condition instance for use with this Lock instance. Condition newCondition() // Acquires the lock only if it is not held by another thread at the time of invocation. booleantryLock() // Try release the lock voidunlock()
ReentrantLock example
By comparing example 1 and example 2, we can clearly understand the role of lock and unlock
Depot is a warehouse. Produce() can produce products into the warehouse, and consume() can consume the products from the warehouse. Mutually exclusive access to the warehouse is achieved through an exclusive lock: before operating (production/consumption) products in the warehouse, the warehouse will be locked by lock() and unlocked by unlock() after the operation.
Producer is a producer class. Call the producer() function in Producer to create a new thread to produce products in the warehouse.
Customer is a consumer class. Call the consume() function in Customer to create a new thread to consume products in the warehouse.
In the main thread main, we will create a new producer mPro and a new consumer mCus. They produce/consume products separately from the warehouse.
Based on the amount of production/consumption in main, the final product left in the warehouse should be 50. The running result is in line with our expectations!
There are two problems with this model:
In reality, the capacity of the warehouse cannot be negative. However, the warehouse capacity in this model can be negative, which contradicts reality!
In reality, the capacity of the warehouse is limited. However, the capacity in this model is indeed unlimited!
We will talk about how to solve these two problems.
Now, let’s look at a simple example 2. By comparing “example 1” and “example 2”, we can more clearly understand the purpose of lock() and unlock().
Example 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
publicclassDepotNoLock{ // size of depot privateint size;
Example 2 removes the lock on the basis of example 1. In example 2, the final remaining product in the warehouse is -60 instead of the 50 as we expected. The reason is that we have not implemented mutually exclusive access to the warehouse.
Example 3
In example 3, we use Condition to solve the two problems in example 1: The size of the warehouse cannot be negative and the capacity of the warehouse is limited.
The solution to this problem is through Condition. Condition needs to be used in conjunction with Lock: through the await() method in Condition, the thread can be blocked [similar to wait()]. Through the signal() method of Condition, the thread can be woken up [similar to notify()].
publicvoidproduce(int val){ lock.lock(); try { // left means the number need to be produced int left = val; while (left > 0) { // when warehouse is full, wait consumer to consume product while (size >= capacity) fullCondition.await();
// get the actual number to be produced (the number to be added to the warehouse) int inc = (size + left) > capacity ? (capacity - size) : left; size += inc; left -= inc; System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, inc, size);
publicvoidconsume(int val){ lock.lock(); try { // left means the number need to be consumed int left = val; while (left > 0) { // when warehouse size is 0, wait producer to produce product while (size <= 0) emptyCondition.await();
// get the actual number to be consumed (the number to be consumed from the warehouse) int dec = Math.min(size, left); size -= dec; left -= dec; System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, dec, size);