¿Cómo usar Java Executor correctamente?

He utilizado Java Executors en mis aplicaciones de subprocesos múltiples, pero parece que no puedo determinar cuándo es el mejor para usar cada una de las siguientes maneras:

1.

ExecutorService executor=Executors.newFixedThreadPool(50); executor.execute(new A_Runner(... some parameter ...)); executor.shutdown(); while (!executor.isTerminated()) { Thread.sleep(100); } 

2.

 int Page_Count=200; ExecutorService executor=Executors.newFixedThreadPool(50); doneSignal=new CountDownLatch(Page_Count); for (int i=0;i<Page_Count;i++) executor.execute(new A_Runner(doneSignal, ... some parameter ...)); doneSignal.await(); executor.shutdown(); while (!executor.isTerminated()) { Thread.sleep(100); } 

3.

 int Executor_Count=30; ThreadPoolExecutor executor=new ThreadPoolExecutor(Executor_Count,Executor_Count*2,1,TimeUnit.SECONDS,new LinkedBlockingQueue()); List<Future> futures=new ArrayList(3330); for (int i=0;i<50;i++) futures.add(executor.submit(new A_Runner(... some parameter ...)); executor.shutdown(); while (!executor.isTerminated()) { executor.awaitTermination(1,TimeUnit.SECONDS); } for (Future future : futures) { String f=future.get(); // ... } 

Específicamente, en [2] ¿qué pasa si omito la señal hecha, entonces será como [1], entonces, ¿para qué sirve la señal done?

Además, en [3], ¿qué sucede si agrego una señal hecha? ¿O es posible?

Lo que me gustaría saber es: ¿estos enfoques son intercambiables, o hay una cierta situación en la que se supone que debo usar un tipo específico anterior?

    1. ExecutorService

      ExecutorService executor=Executors.newFixedThreadPool(50);

      Es simple y fácil de usar. Oculta detalles de bajo nivel de ThreadPoolExecutor .

      Prefiero este cuando el número de tareas Callable/Runnable es pequeño en número y el acumular tareas en la cola ilimitada no aumenta la memoria y degrada el rendimiento del sistema. Si tiene limitaciones de CPU/Memory , use ThreadPoolExecutor con limitaciones de capacidad y RejectedExecutionHandler para manejar el rechazo de tareas.

    2. CountDownLatch

      Has inicializado CountDownLatch con un recuento dado. Este conteo se reduce mediante llamadas al método countDown() . Supongo que está llamando decrement en su tarea Runnable más tarde. Los hilos que esperan que esta cuenta llegue a cero pueden llamar a uno de los métodos await() . La llamada a await() bloquea el hilo hasta que el conteo llega a cero. Esta clase permite que un subproceso Java espere hasta que otro conjunto de subprocesos complete sus tareas.

      Casos de uso:

      1. Alcanzar el Paralelismo Máximo: A veces queremos comenzar una cantidad de hilos al mismo tiempo para lograr el máximo paralelismo

      2. Espere N hilos para completar antes de iniciar la ejecución

      3. Detección de punto muerto

        Eche un vistazo a este artículo de Lokesh Gupta para más detalles.

    3. ThreadPoolExecutor : proporciona más control para ajustar finamente varios parámetros del grupo de subprocesos. Si su aplicación está restringida por el número de Runnable/Callable activas Runnable/Callable , debe usar la cola limitada estableciendo la capacidad máxima. Una vez que la cola alcanza la capacidad máxima, puede definir RejectionHandler. Java proporciona cuatro tipos de políticas RejectedExecutionHandler .

      1. En el valor predeterminado ThreadPoolExecutor.AbortPolicy , el controlador arroja un tiempo de ejecución RejectedExecutionException en el momento del rechazo.

      2. En ThreadPoolExecutor.CallerRunsPolicy , el hilo que invoca ejecutar se ejecuta la tarea. Esto proporciona un mecanismo de control de retroalimentación simple que ralentizará la velocidad con la que se envían las tareas nuevas.

      3. En ThreadPoolExecutor.DiscardPolicy , una tarea que no se puede ejecutar simplemente se descarta.

      4. En ThreadPoolExecutor.DiscardOldestPolicy , si el ejecutor no se cierra, la tarea en la cabecera de la cola de trabajo se elimina y luego se vuelve a intentar la ejecución (que puede fallar nuevamente, haciendo que esto se repita).

        Si desea simular el comportamiento invokeAll() , puede usar el método invokeAll() .

    4. Un mecanismo más que no citaron es ForkJoinPool

      The ForkJoinPool se agregó a Java en Java 7. The ForkJoinPool es similar al Java ExecutorService pero con una diferencia. El ForkJoinPool facilita que las tareas dividan su trabajo en tareas más pequeñas que luego se envían al ForkJoinPool también. El robo de tareas ocurre en ForkJoinPool cuando los hilos de trabajo libres roban tareas de la cola de hilos de los trabajadores ocupados.

      Java 8 ha introducido una API más en ExecutorService para crear pool de robo de trabajo. No tiene que crear RecursiveTask y RecursiveAction pero aún puede usar ForkJoinPool .

        public static ExecutorService newWorkStealingPool() 

      Crea un grupo de subprocesos de robo de trabajo utilizando todos los procesadores disponibles como su nivel de paralelismo de destino.

      Por defecto, tomará el número de núcleos de CPU como parámetro.

    Todos estos cuatro mecanismos son complementarios entre sí. Dependiendo del nivel de granularidad que desee controlar, debe elegir los correctos.