Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread.sleep inside infinite while loop in lambda doesn't require 'catch (InterruptedException)' - why not?

My question is about InterruptedException, which is thrown from the Thread.sleep method. While working with ExecutorService I noticed some weird behaviour that I don't understand; here is what I mean:

ExecutorService executor = Executors.newSingleThreadExecutor();     executor.submit(() -> {         while(true)         {             //DO SOMETHING             Thread.sleep(5000);         }     }); 

With this code, the compiler doesn't give me any error or message that InterruptedException from Thread.sleep should be caught. But when I am trying to change the loop condition and replace "true" with some variable like this:

ExecutorService executor = Executors.newSingleThreadExecutor();     executor.submit(() -> {         while(tasksObserving)         {             //DO SOMETHING             Thread.sleep(5000);         }     }); 

The compiler constantly complains that InterruptedException has to be handled. Can someone explain to me why this happens, and why if the condition is set to true the compiler ignores the InterruptedException?

like image 488
schemaboi Avatar asked May 15 '19 12:05

schemaboi


People also ask

What does the InterruptedException thrown by thread sleep() mean?

the Thread.sleep () method can throw an InterruptedException, and I want to be sure I know what this exception implies and therefore how to handle it. My understanding is that this exception if thrown, could mean that the sleep () has terminated before the specified duration has completed. Is this correct?

What is the use of sleep method in Thread class in Java?

Thread Class is a class that is basically a thread of execution of the programs. It is present in Java.lang package. Thread class contains the Sleep () method. There are two overloaded methods of Sleep () method present in Thread Class, one is with one argument and another one is with two arguments.

How to throw InterruptedException in Java?

There are several methods in Java that throw InterruptedException. These include Thread.sleep (), Thread.join (), the wait () method of the Object class, and put () and take () methods of BlockingQueue, to name a few. 3.3. Interruption Methods in Threads

How do I check if a thread has been interrupted?

Thread provides the interrupt () method for interrupting a thread, and to query whether a thread has been interrupted, we can use the isInterrupted () method. Occasionally, we may wish to test whether the current thread has been interrupted and if so, to immediately throw this exception.


1 Answers

The reason for this, is that these invocations are in fact, invocations to two different overloaded methods available in ExecutorService; each of these methods taking a single argument of different types:

  1. <T> Future<T> submit(Callable<T> task);
  2. Future<?> submit(Runnable task);

Then what happens is that the compiler is converting the lambda in the first case of your problem into a Callable<?> functional interface (invoking the first overloaded method); and in the second case of your problem converts the lambda into a Runnable functional interface (invoking therefore the second overloaded method), requiring because of this to handle the Exception thrown; but not in the previous case using the Callable.

Although both functional interfaces don't take any arguments, Callable<?> returns a value:

  1. Callable: V call() throws Exception;
  2. Runnable: public abstract void run();

If we switch to examples that trim the code to the relevant pieces (to easily investigate just the curious bits) then we can write, equivalently to the original examples:

    ExecutorService executor = Executors.newSingleThreadExecutor();      // LAMBDA COMPILED INTO A 'Callable<?>'     executor.submit(() -> {         while (true)             throw new Exception();     });      // LAMBDA COMPILED INTO A 'Runnable': EXCEPTIONS MUST BE HANDLED BY LAMBDA ITSELF!     executor.submit(() -> {         boolean value = true;         while (value)             throw new Exception();     }); 

With these examples, it may be easier to observe that the reason why the first one is converted to a Callable<?>, while the second one is converted to a Runnable is because of compiler inferences.

In both cases, the lambda bodies are void-compatible, since every return statement in the block has the form return;.

Now, in the first case, the compiler does the following:

  1. Detects that all execution paths in the lambda declare throwing checked exceptions (from now on we will refer as 'exception', implying only 'checked exceptions'). This includes the invocation of any method declaring throwing exceptions and the explicit invocation to throw new <CHECKED_EXCEPTION>().
  2. Concludes correctly that the WHOLE body of the lambda is equivalent to a block of code declaring throwing exceptions; which of course MUST be either: handled or re-thrown.
  3. Since the lambda is not handling the exception, then the compiler defaults to assume that these exception(s) must be re-thrown.
  4. Safely infers that this lambda must match a functional interface cannot complete normally and therefore is value-compatible.
  5. Since Callable<?> and Runnable are potential matches for this lambda, the compiler selects the most specific match (to cover all scenarios); which is the Callable<?>, converting the lambda into an instance of it and creating an invocation reference to the submit(Callable<?>) overloaded method.

While, in the second case, the compiler does the following:

  1. Detects that there may be execution paths in the lambda that DO NOT declare throwing exceptions (depending on to-be-evaluated logic).
  2. Since not all execution paths declare throwing exceptions, the compiler concludes that the body of the lambda is NOT NECESSARILY equivalent to a block of code declaring throwing exceptions - compiler doesn't care/pay attention if some portions of the code do declare that they may, only if the whole body does or not.
  3. Safely infers that the lambda is not value-compatible; since it MAY complete normally.
  4. Selects Runnable (as it is the only available fitting functional interface for the lambda to be converted into) and creates an invocation reference to the submit(Runnable) overloaded method. All this coming at the price of delegating to the user, the responsibility of handling any Exceptions thrown wherever they MAY occur within portions of the lambda body.

This was a great question - I had a lot of fun chasing it down, thanks!

like image 77
Marco R. Avatar answered Oct 08 '22 18:10

Marco R.