The difference between multi-threaded lock and lockInterruptibly
In multithreaded programming, locks are a key tool used to ensure that multiple threads can maintain consistency and correctness when accessing shared resources.
Java provides a variety of mechanisms to implement locks, the most commonly used ones areReentrantLock
。ReentrantLock
Two ways to acquire locks are provided:lock
andlockInterruptibly
。
The two behave differently when dealing with thread interrupts, and understanding these differences is essential for writing robust multithreaded programs.
lock method
Sample code:
public static void lock() { Lock lock = new ReentrantLock(); try { (); Thread t1 = new Thread(() -> { Thread currentThread = (); try { (); (() + "Code after lock"); } finally { (); } ("currentThread = " + () + " isInterrupted:" + ()); }, "t1"); (); (); ("currentThread = " + ().getName()); } finally { (); } }
Execution results:
currentThread = main
The code after t1 lock
currentThread = t1 isInterrupted:true
explain:
In this example, the main thread (main
) First acquires the lock. Then start a new thread (t1
), the thread tries to acquire the same lock again. The main thread is startingt1
Call it immediately afterward()
Method interruptt1
Thread.
althought1
The thread was interrupted, but it still successfully acquired the lock and executed the code after the lock. This is becauselock
The method ignores the interrupt state of the thread when acquiring the lock. As long as the lock is available, it will acquire the lock. In other words, even if the thread is interrupted, it continues to wait for the lock to be acquired until it succeeds.
lockInterruptibly method
Sample code:
public static void lockInterruptiblyDemo() { Lock lock = new ReentrantLock(); try { (); Thread t1 = new Thread(() -> { Thread currentThread = (); try { (); (() + "Code after lockInterruptibly"); } catch (InterruptedException e) { (); } finally { (); } ("currentThread = " + () + " isInterrupted:" + ()); }, "t1"); (); (); ("currentThread = " + ().getName()); } finally { (); } }
Execution results:
currentThread = main
at (:1220)
at (:335)
at $lockInterruptiblyDemo$1(:39)
at (:748)
Exception in thread "t1"
at $(:151)
at (:1261)
at (:457)
at $lockInterruptiblyDemo$1(:44)
at (:748)
explain:
In this example,t1
The thread was called when it tried to acquire the locklockInterruptibly
method. andlock
Different methods,lockInterruptibly
The interrupt status of the thread is checked when trying to acquire the lock. If the thread is interrupted, it will be thrown immediatelyInterruptedException
Exception, without continuing to wait for the lock to be acquired.
The execution results are displayed.t1
The thread is interrupted when trying to acquire the lock and thrownInterruptedException
. Because the exception is caught and printed,t1
The thread does not acquire the lock, which avoids the deadlock problem that may occur in some cases.
Summary of the difference
Interrupt response:
-
lock
Method: If the interrupt is not responding to, the thread will try to acquire the lock until it succeeds. -
lockInterruptibly
Method: In response to an interrupt, the thread will throw it after detecting an interrupt.InterruptedException
And stop trying to acquire the lock.
Use scenarios:
-
lock
The method is suitable for scenarios where interrupts are not considered. -
lockInterruptibly
The method is suitable for scenarios where interrupts need to be responded to in a timely manner, such as when it is necessary to be able to interrupt threads to avoid deadlocks.
Interview Question: Why are queues in AQS designed as bidirectional linked lists?
AQS (AbstractQueuedSynchronizer) is the basic framework of locks and synchronizers in Java concurrency packages. AQS uses a FIFO (first-in, first-out) bidirectional linked list to manage queues waiting for threads.
reason:
Two-way traversal:
- When a node (thread) is awakened, it is necessary to be able to easily find the predecessor node to ensure the wake-up order and correctness of the threads.
- The bidirectional linked list allows easy access to the front-drive node from the current node, thus enabling the state of the front-drive node to be modified.
Node deletion:
- In some cases, the node (thread) may need to be canceled from the queue.
- Bidirectional linked lists make deletion node operation more efficient, because they can be relinked directly through the front-drive and successor nodes without traversing from the beginning.
Status update:
- The bidirectional linked list structure makes it easier to update the node state (such as changing the node from the waiting state to the running state)
- Can efficiently propagate state information between nodes
Implement complexity:
- Although the two-way linked list is a little more complicated to implement
- But the flexibility and operational efficiency improvement provided is worthwhile in high concurrency scenarios
With this design, AQS can manage thread queues more efficiently, ensuring high performance and correctness of locks and synchronizers.
Detailed analysis: Bidirectional link table in AQS
AQS is a node-based framework, where all threads waiting to acquire locks are encapsulated into a node and added to the waiting queue. The structure and operation of this queue directly affect the performance and responsiveness of the lock.
The main node structures in AQS:
Each node contains the following main parts:
- Front-drive node: Point to the previous node waiting for thread.
- Successor node: Point to the next node waiting for thread.
- Thread reference: Contains a reference to the thread waiting to acquire the lock.
- state: Indicates the waiting state of the thread (such as waiting, cancel, etc.).
Operation example:
Join the team:
- When a thread requests to acquire a lock but the lock is unavailable, it is encapsulated into a node and added to the end of the queue.
- The bidirectional linked list structure allows fast positioning of the queue and linking new nodes.
Departure:
- When a node is awakened (usually acquiring a lock), it needs to be removed from the queue.
- The bidirectional linked list structure allows efficient relinking and deletion of nodes through the front-drive and successor nodes.
Status propagation:
- When a node's state changes (such as from waiting to running), it needs to notify the predecessor or successor node.
- A bidirectional linked list allows efficient propagation of state between nodes, ensuring that every thread in the queue responds to state changes correctly.
Summarize
understandlock
andlockInterruptibly
The difference helps us choose the appropriate lock acquisition method in multi-threaded programming to ensure the robustness and responsiveness of the program. The bidirectional linked list design in AQS is an important basis for ensuring that locks and synchronizers efficiently manage waiting thread queues. This design makes thread management more efficient and flexible, and is particularly outstanding in high concurrency scenarios. By deeply understanding these underlying mechanisms, we can better write high-performance multi-threaded applications.
The above is personal experience. I hope you can give you a reference and I hope you can support me more.