java multithreading executorservice java.util.concurrent threadpoolexecutor

¿Cómo usar Java Executor correctamente?



multithreading executorservice (1)

  1. ExecutorService

    ExecutorService executor=Executors.newFixedThreadPool(50);

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

    Prefiera esta Callable/Runnable cuando el número de tareas Callable/Runnable es pequeño y la acumulación de tareas en la cola no Callable/Runnable no aumenta la memoria ni degrada el rendimiento del sistema. Si tiene restricciones de CPU/Memory , use ThreadPoolExecutor con restricciones de capacidad y RejectedExecutionHandler para manejar el rechazo de tareas.

  2. CountDownLatch

    Inicializó CountDownLatch con un recuento determinado. Este recuento se reduce mediante llamadas al método countDown() . Supongo que más tarde estás llamando a decremento en tu tarea Runnable. Los subprocesos que esperan que este recuento llegue a cero pueden llamar a uno de los métodos de await() . Llamar a await() bloquea el hilo hasta que el recuento llegue 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 serie de hilos al mismo tiempo para lograr el paralelismo máximo

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

    3. Detección de punto muerto.

      Echa un vistazo a este article de Lokesh Gupta para más detalles.

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

    1. En el ThreadPoolExecutor.AbortPolicy predeterminado, el controlador ThreadPoolExecutor.AbortPolicy RejectedExecutionException en tiempo de ejecución tras el rechazo.

    2. En ThreadPoolExecutor.CallerRunsPolicy , el hilo que invoca ejecutarse ejecuta la tarea. Esto proporciona un mecanismo de control de retroalimentación simple que reducirá la velocidad de envío de nuevas tareas.

    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 descarta y, a continuación, se vuelve a intentar la ejecución (que puede fallar nuevamente, lo que puede repetir esto)

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

  4. Un mecanismo más que no citó es ForkJoinPool

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

    Java 8 ha introducido una API más en ExecutorService para crear un grupo 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 objetivo.

    Por defecto, tomará varios 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.

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

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<String>> 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<String> future : futures) { String f=future.get(); // ... }

Específicamente, en [2], ¿qué pasa si me salto el doneSignal, entonces será como [1], entonces, ¿de qué sirve el doneSignal?

Además, en [3], ¿qué sucede si agrego doneSignal? ¿O es posible?

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