Can a synchronized block/method be interrupted?
Asked Answered
S

5

6

While I know the theoretical differences between Re-EntrantLocks and synchronized, I'm confused to the below point.

See this statement from an article on Javarevisited comparing synchronized and Lock objects:

One more worth noting difference between ReentrantLock and synchronized keyword in Java is, ability to interrupt Thread while waiting for Lock. In case of synchronized keyword, a thread can be blocked waiting for lock, for an indefinite period of time and there was no way to control that. ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt thread when it is waiting for lock. Similarly tryLock() with timeout can be used to timeout if lock is not available in certain time period.

As per the above statement, I did try interrupting the Thread waiting() on synchronized method (i.e blocking wait) and it did throw an InterruptedException. But this behavior is contradictory with what is stated in the above statement.

// this method is called from inside run() method of every thread. 
public synchronized int getCount() {
        count++;
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " gets " + count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return count;
}

....
....
t1.start();
t2.start();
t3.start();
t4.start();

t2.interrupt();

Here is the output that I got :

Thread 1 gets 1
Thread 4 gets 2
Thread 3 gets 3  
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at locks.SynchronizedLockInterrupt.getCount(SynchronizedLockInterrupt.java:10)  
    at locks.SynchronizedLockInterrupt$2.run(SynchronizedLockInterrupt.java:35)  
    at java.lang.Thread.run(Unknown Source)  

I'm confused if my example is not correct or the quoted statement about synchronized() is incorrect?

Stedmann answered 23/8, 2014 at 11:33 Comment(0)
M
4

Without the rest of the code this question might not be fully answered. What, I think, you're being confused with here is that you're seeing that, whilst the code would imply you cannot "interrupt" a thread that's blocked on a synchronized lock you are seeing that your count variable seems to be unaffected by the thread which is supposed to have entered into this method.

Important to note that you can technically "interrupt" a blocked thread, as in you can call interrupt() on it and this will set the interrupted flag. Just because a Thread has the interrupted flag set does not mean that it cannot execute any more code. Simply, when it get's to the next code that checks for an interrupted state, that code will likely throw an InterruptedException whilst clearing the flag at the same time. If the person catching the exception intends to do more work, it's their (almost moral) duty to re-set the flag or throw the same.

So, yes, in your example, you are catching the exception that has been thrown by .sleep() on entry, likely before the thread was sleep-ed, you then print the stack trace that proves that.

The outstanding question that might be causing confusion for you; why, then, did my count not increment if this code was allowed to run until the .sleep() method call?

The answer is that the count variable was incremented, you just didn't see the result.

synchronized in Java does not guarantee order and can lead to starvation so t2 just happened to be executed last and you never checked the count before you slept to see that it was already 3

So to answer your question, the documentation is correct and the behaviour is correct.

Interrupting a thread which is waiting "uninterruptedly" on a Lock , ReentrantLock or synchronized block will merely result in the thread waking up and seeing if it's allowed to take the lock yet, by whatever mechanism is in place in the defining lock, and if it cannot it parks again until it is interrupted again or told it can take the lock. When the thread can proceed it simply proceeds with its interrupted flag set.

Contrast to lockInterruptibly where, actually, if you are interrupted, you do not ever get the lock, and instead you "abort" trying to get the lock and the lock request is cancelled.

lock and lockInterruptibly can be mixed use on the same ReentrantLock as the lock will manage the queue and skip requests that were CANCELLED by a finally statement because they were interrupted when waiting on a lock.

In summary:

  • You can almost always interrupt a thread.
  • The interrupt flag is usually only cleared on a thread by code that documents that it clears the flag when throwing the InterruptedException , but not all code documents this (lockInterruptibly on ReentrantLock does, but not the same on AbstractQueuedSynchronizer which powers the lock).
  • Interrupting a thread has different behaviour depending on what it is doing at the time;
    • A parked thread will be un-parked and have it's flag set, usually then cleared
    • A thread waiting on a lock / synchronized block will eventually get into the code but with interrupted flag set
    • A thread waiting on a lockInterruptibly or a get on a future etc will be unparked and behave as documented, aborting the lock acquisition.
Mikesell answered 21/3, 2019 at 9:39 Comment(0)
K
3

synchronized is an intrinsic lock which is beyond the control of JDK.

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.") Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.

When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.

In your example, you are actually interrupting the sleep as JDK doc mentions.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

More details about how interrupt() works.

Many methods that throw InterruptedException, such as sleep, are designed to cancel their current operation and return immediately when an interrupt is received.

Kinnikinnick answered 19/5, 2019 at 10:6 Comment(0)
H
0

If have added a simple example to make it clear.

In your example you have already aquired the lock, see your stacktrace. The code is self explaining.

The problem with synchronized is that it is no interruption point, whereas lock.lockInterruptibly() is. Note that lock.lock() is also not an interruption point.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Foo {

    public static void main(String[] args) throws InterruptedException {

        // for the example with synchronized
        Object monitor = new Object();
        // for the example with locks
        Lock lock = new ReentrantLock();

        // iam lazy, just use both lock and motitor for this example
        Thread one = new Thread(() -> {
            lock.lock();
            try {
                synchronized (monitor) {
                    System.out.println("Thread one entered monitor");
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        System.out.println("Thread one interrupted");
                        Thread.currentThread().interrupt();
                    }
                }
            } finally {
                lock.unlock();
            }
        });

// uncomment to use the monitor object
//        Thread two = new Thread(() -> {
//            synchronized (monitor) {
//                System.out.println("Thread two entered monitor");
//            }
//        });

        Thread two = new Thread(() -> {
            try {
                lock.lockInterruptibly();
                try {
                    System.out.println("Thread one entered lock");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("Thread two interrupted while waiting for lock");
                Thread.currentThread().interrupt();
            }
        });

        // start thread one
        one.start();
        // wait for the thread to start, too lazy to implement notifications
        Thread.sleep(1000);

        // start thread two
        two.start();
        // interrupting will wait until thread one finished
        two.interrupt();
    }
}
Handshake answered 21/3, 2019 at 12:16 Comment(0)
V
0

If you remove "Thread.sleep(3000)", your 'getCount()' method will not throw exception.

You can only interrupt a thread either in sleep or wait in case of Synchronised method

Vansickle answered 27/4, 2021 at 13:37 Comment(0)
R
-1

You're not interrupting the synchronization, you're interrupting the sleep().

Receptor answered 23/8, 2014 at 11:36 Comment(5)
Please look at my code. Thread 2 once started is waiting on synchronized() because other thread currently running getCount() has sleep. I'm calling t.interrupt() on waiting thread, not sleeping thread.Stedmann
The exception says java.lang.InterruptedException: sleep interrupted atAden
@peeppeep Yes, you're calling interrupt() on a waiting thread, which does nothing. When the thread finally gets the lock, it goes to the sleep block and is immediately interrupted. If there wasn't a sleep() (or another interruptable operation), there would be no effect. However with a ReentrantLock you can interrupt the thread while it's waiting to acquire the lock.Receptor
This does not actually answer the question since it does not explain whether the quote is wrong or where the questioner misunderstood the explained behavior. Downvoted until improved.Quickel
@Quickel oh look at the amount of fudges I give, they are zero.Receptor

© 2022 - 2024 — McMap. All rights reserved.