SoFunction
Updated on 2025-04-09

Multithreading-Differences and Descriptions of Lock Interruptibly

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 areReentrantLockReentrantLockTwo ways to acquire locks are provided:lockandlockInterruptibly

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 startingt1Call it immediately afterward()Method interruptt1Thread.

althought1The thread was interrupted, but it still successfully acquired the lock and executed the code after the lock. This is becauselockThe 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,t1The thread was called when it tried to acquire the locklockInterruptiblymethod. andlockDifferent methods,lockInterruptiblyThe interrupt status of the thread is checked when trying to acquire the lock. If the thread is interrupted, it will be thrown immediatelyInterruptedExceptionException, without continuing to wait for the lock to be acquired.

The execution results are displayed.t1The thread is interrupted when trying to acquire the lock and thrownInterruptedException. Because the exception is caught and printed,t1The thread does not acquire the lock, which avoids the deadlock problem that may occur in some cases.

Summary of the difference

Interrupt response

  • lockMethod: If the interrupt is not responding to, the thread will try to acquire the lock until it succeeds.
  • lockInterruptiblyMethod: In response to an interrupt, the thread will throw it after detecting an interrupt.InterruptedExceptionAnd stop trying to acquire the lock.

Use scenarios

  • lockThe method is suitable for scenarios where interrupts are not considered.
  • lockInterruptiblyThe 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

understandlockandlockInterruptiblyThe 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.