Thread.sleep infinita dentro bucle while en lambda no requiere 'pesca (InterruptedException)' - por qué no?

schemaboi:

Mi pregunta es sobre InterruptedException, que se lanza desde el Thread.sleepmétodo. Mientras se trabaja con ExecutorServicelo observado un comportamiento extraño que no entiendo; esto es lo que quiero decir:

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

Con este código, el compilador no me da ningún mensaje de error o que InterruptedExceptionde Thread.sleepdeben ser capturados. Pero cuando estoy tratando de cambiar la condición del bucle y reemplazar "verdadero" con alguna variable como esto:

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

El compilador se queja constantemente de que InterruptedExceptiontiene que ser manejado. ¿Puede alguien explicarme por qué sucede esto, y por qué si la condición se establece en true el compilador ignora la InterruptedException?

Marco R. :

La razón de esto, es que estas invocaciones son, de hecho, las invocaciones a dos métodos diferentes disponibles en sobrecargado ExecutorService; cada uno de estos métodos que toman un único argumento de diferentes tipos:

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

Entonces lo que sucede es que el compilador es la conversión de la lambda en el primer caso de su problema en una Callable<?>interfaz funcional (invocando el método primera sobrecargada); y en el segundo caso de su problema convierte la lambda en una Runnableinterfaz funcional (invocando por lo tanto, el segundo método sobrecargado), requiriendo a causa de esto para manejar el Exceptionlanzado; pero no en el caso anterior con el Callable.

Aunque ambas interfaces funcionales no tienen ningún argumento, Callable<?> devuelve un valor :

  1. exigible: V call() throws Exception;
  2. ejecutable: public abstract void run();

Si cambiamos a ejemplos que recortan el código para las piezas pertinentes (para investigar fácilmente sólo los bits curiosos), entonces podemos escribir, lo que es equivalente a los ejemplos originales:

    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();
    });

Con estos ejemplos, puede ser más fácil de observar que la razón por la cual el primero se convierte en una Callable<?>, mientras que el segundo se convierte en una Runnablees a causa de las inferencias del compilador .

En ambos casos, los cuerpos son lambda -vacío compatible , ya que cada instrucción de retorno en el bloque tiene la forma return;.

Ahora, en el primer caso, el compilador hace lo siguiente:

  1. Detecta que todas las rutas de ejecución en el lanzamiento lambda declarar las excepciones comprobadas (de ahora en adelante nos referiremos como 'excepción' , lo que implica solamente 'comprueban excepciones' ). Esto incluye la invocación de cualquier método declarar excepciones que lanzan y la invocación explícita a throw new <CHECKED_EXCEPTION>().
  2. Concluye correctamente que el TODA cuerpo de la lambda es equivalente a un bloque de código que se declara excepciones de lanzamiento; que por supuesto DEBE ser: manipulados o re-lanzado.
  3. Dado que el lambda no está manejando la excepción, entonces los valores por defecto del compilador asumir que estos excepción (s) debe ser re-lanzado.
  4. Con seguridad infiere que este lambda debe coincidir con una interfaz funcional no puede complete normallyy por lo tanto es compatible con valor .
  5. Dado Callable<?>y Runnableson posibles coincidencias de este lambda, el compilador selecciona la coincidencia más específica (para cubrir todos los escenarios); que es el Callable<?>, la conversión de la lambda en una instancia del mismo y la creación de una referencia de invocación al submit(Callable<?>)método sobrecargado.

Mientras que, en el segundo caso, el compilador hace lo siguiente:

  1. Detecta que puede haber rutas de ejecución de la lambda que NO declarar lanzar excepciones (en función de la lógica a-ser-evaluado ).
  2. Dado que no todos los caminos de ejecución declaran que lanzan excepciones, el compilador llega a la conclusión de que el cuerpo de la lambda es NO NECESARIAMENTE equivalente a un bloque de código que declara lanzar excepciones - compilador no le importa / prestar atención si algunas partes del código declaramos que puedan , sólo si todo el cuerpo hace o no.
  3. Con seguridad infiere que el lambda no es compatible con valor ; ya que MAY complete normally .
  4. Selecciona Runnable(ya que es la única disponible apropiado interfaz funcional para el lambda para ser convertido en) y crea una referencia de invocación al submit(Runnable)método sobrecargado. Todo esto viene al precio de delegar en el usuario, la responsabilidad de manejar cualquier Exceptions arrojado dondequiera que PUEDEN ocurrir dentro de las partes del cuerpo lambda.

Esta fue una gran pregunta - que tenía un montón de diversión persiguiendo abajo, gracias!

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=227261&siteId=1
Recomendado
Clasificación