¿Por qué “header.get () + footer.get ()” resulta en un interlocking, cuando se usa un Ejecutor de un solo hilo?

Esto es listado 8.1 en Java concurrencia en la práctica :

public class ThreadDeadlock { ExecutorService exec = Executors.newSingleThreadExecutor(); public class RenderPageTask implements Callable { public String call() throws Exception { Future header, footer; header = exec.submit(new LoadFileTask("header.html")); footer = exec.submit(new LoadFileTask("footer.html")); String page = renderBody(); //Will deadlock -- task waiting for result of subtask return header.get() + page + footer.get(); } } } 

Está dentro

Capítulo 8: Grupos de subprocesos > Sección 8.1.1 Punto muerto de inanición del hilo

y tiene el título:

“Tarea que pone punto muerto en un Executor un solo hilo. No hagas esto.

¿Por qué esto resulta en un punto muerto? Pensé que se llama a header.get() , y luego se llama a footer.get() , y cada resultado se agrega a la cadena. ¿Por qué un solo Ejecutor con hilos no sería suficiente para ejecutar estos uno después del otro?

Texto relevante del capítulo:

8.1.1 Punto muerto de hambre del hilo

Si las tareas que dependen de otras tareas se ejecutan en un grupo de subprocesos, pueden interrumpirse. En un ejecutor de un solo hilo, una tarea que envía otra tarea al mismo ejecutor y espera su resultado siempre estará en un punto muerto. La segunda tarea se encuentra en la cola de trabajo hasta que la primera tarea se completa, pero la primera no se completará porque está esperando el resultado de la segunda tarea. Lo mismo puede suceder en grupos de subprocesos más grandes si todos los subprocesos están ejecutando tareas que están bloqueadas en espera de otras tareas aún en la cola de trabajo. Esto se denomina punto muerto de hambre de subproceso , y puede ocurrir siempre que una tarea de grupo inicie una espera de locking ilimitada para algún recurso o condición que pueda tener éxito solo mediante la acción de otra tarea de grupo, como esperar el valor de retorno o el efecto secundario de otra tarea. a menos que pueda garantizar que la piscina es lo suficientemente grande.

ThreadDeadlock en el Listado 8.1 ilustra el punto muerto de hambre de subprocesos. RenderPageTask envía dos tareas adicionales al Executor obtener el encabezado y el pie de página, representa el cuerpo de la página, espera los resultados del encabezado y las tareas del pie de página, y luego combina el encabezado, el cuerpo y el pie de página en la página final. Con un ejecutor de un solo subproceso, ThreadDeadlock siempre se ThreadDeadlock . De manera similar, las tareas que se coordinan entre sí con una barrera también podrían causar un punto muerto en la hambruna de hilos si el grupo no es lo suficientemente grande.

El punto muerto real se producirá tan pronto como se RenderPageTask una instancia de RenderPageTask a la misma instancia del ejecutor donde envía su tarea.

Por ejemplo, agregar

 exec.submit(new RenderPageTask()); 

y experimentarás un punto muerto.

Por supuesto, esto puede considerarse un problema del código circundante (es decir, simplemente podría definir y documentar que su RenderPageTask no debe enviarse a esta instancia del ejecutor), pero un buen diseño evitaría tales inconvenientes por completo.

Una posible solución para esto sería usar ForkJoinPool , que usa el robo de trabajo para evitar esta forma de posibles interlockings.

Sí, apuesto a que RenderPageTask se envía al mismo grupo de ejecutores que otras tareas, por lo que otras tareas no comenzarán hasta que RenderPageTask termine, pero esto nunca sucederá, tenemos un punto muerto.