This article is from the Huawei cloud community “Synchronized in Java, why provide Lock?” , author: Ice River.
The synchronized keyword is provided in Java to ensure that only one thread can access a synchronized code block. Since the synchronized keyword is provided, why does the Java SDK package provide a Lock interface? Is this a duplication of the wheel? Why did the Java designers do this? Today, we will discuss this problem together.
Why provide Lock interface?
Many of you may have heard that synchronized didn’t perform as well as Lock in Java version 1.5, but since Java version 1.6, synchronized has made a lot of improvements. So why use Lock when the synchronized keyword has improved?
If we think about it more deeply, it’s not hard to realize that synchronized locking can’t be actively released, which leads to deadlocks.
The deadlock problem
If a deadlock is to occur, the following four conditions must exist, none of which is necessary.
- Mutual exclusion conditions
A resource is occupied by only one thread at a time. If another thread requests the resource, the requesting thread can only wait.
- Inalienable condition
A resource acquired by a thread cannot be seized by another thread before it is fully used, that is, it can only be released by the thread that acquired the resource itself (only on its own initiative).
- Request and hold conditions
A thread that has already held at least one resource makes a request for a new resource that has already been occupied by another thread. The requester thread blocks but holds on to its acquired resource.
- Cyclic waiting condition
There must be a process waiting queue {P1,P2… ,Pn}, where P1 waits for the resource occupied by P2, and P2 waits for the resource occupied by P3… Pn waits for the resources occupied by P1 to form a process waiting loop, in which the resources occupied by each process are applied for by another process at the same time, that is, the former process occupies the resources affectionately occupied by the latter process.
Limitations of synchronized
If our program deadlocks with the synchronized keyword, the synchronized key cannot break the condition of the “inalienable” deadlock. This is because synchronized can block a thread if it fails to acquire a resource, and when synchronized blocks, it can do nothing and cannot release the resource it already possesses.
In most cases, however, we want the “inalienable” condition to be broken. In other words, the condition of “inalienable” is broken. When the thread occupying part of the resource applies for other resources, if it fails to apply for other resources, it can actively release the resources it occupies.
If we were to redesign the lock ourselves to solve the synchronized problem, how would we design it?
To solve the problem
Given the limitations of synchronized, what if we were to implement a lock ourselves? That said, how do we address the limitations of synchronized when designing locks? Here, I think there are three ways to think about it.
(1) Able to respond to interruptions. The problem with synchronized is that after holding lock A, if an attempt to acquire lock B fails, the thread enters A blocked state, and once A deadlock occurs, there is no opportunity to wake up the blocked thread. But if the blocked thread can respond to the interrupt signal, that is, wake it up when we send the interrupt signal to the blocked thread, then it has A chance to release lock A that it once held. So you’re breaking the inalienable condition.
(2) Support timeout. If the thread does not acquire the lock for a period of time, instead of entering a blocking state, it returns an error, then the thread has a chance to release the lock it once held. You can also destroy the inalienable condition.
(3) Lock acquisition without blocking. If an attempt to acquire the lock fails and the thread does not block but returns, the thread has a chance to release the lock it once held. You can also destroy the inalienable condition.
The Lock interface provides three methods, as shown below.
Void lockInterruptibly() throws InterruptedException; // Support timeout API Boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // API for non-blocking lock acquisition Boolean tryLock();Copy the code
- lockInterruptibly()
Support interrupts.
- TryLock () method
The tryLock() method returns a value indicating that it was used to attempt to acquire the lock, returning true on success, or false on failure (that is, the lock was acquired by another thread), meaning that the method will return immediately anyway. You don’t wait around until you get the lock.
- TryLock (long time, TimeUnit unit) method
The tryLock(long time, TimeUnit Unit) method is similar to the tryLock() method, except that it waits a certain amount of time before the lock is available and returns false if the lock is not available within the time limit. Returns true if the lock was acquired initially or while waiting.
In other words, for deadlock problems, locks can break an inalienable condition, for example, our code below breaks the inalienable condition of a deadlock.
public class TansferAccount{ private Lock thisLock = new ReentrantLock(); private Lock targetLock = new ReentrantLock(); Private Integer balance; Public void Transfer (TansferAccount Target, Integer transferMoney){Boolean isThisLock = thisLock.tryLock(); if(isThisLock){ try{ boolean isTargetLock = targetLock.tryLock(); if(isTargetLock){ try{ if(this.balance >= transferMoney){ this.balance -= transferMoney; target.balance += transferMoney; } }finally{ targetLock.unlock } } }finally{ thisLock.unlock(); }}}}Copy the code
An exception is that a ReentrantLock has a ReentrantLock underneath it, and ReentrantLock supports both fair and unfair locks.
When ReentrantLock is used, there are two constructors in ReentrantLock, one that takes no arguments and one that passes in the fair argument. The fair argument represents a fair policy for the lock, passing true to indicate that a fair lock needs to be constructed, or an unfair lock needs to be constructed. See the following code snippet.
Public ReentrantLock() {sync = new NonfairSync(); // No argument constructor: default nonfairlock public ReentrantLock() {sync = new NonfairSync(); } public ReentrantLock(Boolean fair){sync = fair? new FairSync() : new NonfairSync(); }Copy the code
In essence, the implementation of a lock corresponds to an entry wait queue. If a thread does not acquire the lock, it will enter the wait queue. When a thread releases the lock, it needs to wake up a waiting thread from the wait queue. If it’s a fair lock, the wake-up strategy is to wake up whoever waited long, fair enough; If an unfair lock is not provided, it is possible that the thread with the short wait time will be woken up first. Whereas Lock supports fair locks, synchronized does not.
Finally, it’s worth noting that when you use Lock to Lock, be sure to release the Lock in the finally{} code block, as shown in the code snippet below.
try{
lock.lock();
}finally{
lock.unlock();
}
Copy the code
Note: other synchronized and Lock detailed description, partners can consult by themselves.
Click to follow, the first time to learn about Huawei cloud fresh technology ~