Red de conocimiento informático - Material del sitio web - Cuatro formas de implementar subprocesos múltiples

Cuatro formas de implementar subprocesos múltiples

Cuatro formas de implementar subprocesos múltiples: subprocesos desnudos, servicio Ejecutor, marco ForkJoin y modelo Actor.

1. Subproceso desnudo

El subproceso es la unidad de concurrencia más básica. Los subprocesos de Java se asignan esencialmente a subprocesos del sistema operativo y cada objeto de subproceso corresponde a un subproceso de computadora subyacente. Cada subproceso tiene su propio espacio de pila, que ocupa una parte designada del espacio de proceso JVM.

La interfaz del hilo es bastante simple. Solo necesita proporcionar un Runnable y llamar al inicio para iniciar el cálculo. No existe una API lista para usar para finalizar un hilo, debe implementarla usted mismo.

La ventaja es que está muy cerca del modelo de sistema operativo/hardware de computación concurrente, y este modelo es muy simple. Varios subprocesos se ejecutan y se comunican a través de la memoria compartida. La mayor desventaja es que es fácil para los desarrolladores prestar demasiada atención a la cantidad de subprocesos.

Los hilos son objetos caros y crearlos requiere mucha memoria y tiempo. Esto es una contradicción. Si tiene muy pocos subprocesos, no podrá obtener una buena concurrencia; si tiene demasiados subprocesos, probablemente causará problemas de memoria y la programación se volverá más complicada. Si necesitas una solución rápida y sencilla, definitivamente puedes utilizar este método, no lo dudes.

2. Servicio ejecutor

Otra opción es utilizar una API para gestionar un grupo de hilos. Afortunadamente, la JVM nos proporciona dicha función, que es la interfaz Ejecutor. Oculta los detalles de cómo se maneja Runnable.

Simplemente dice: "¡Desarrollador! ¡Dame la tarea y yo la manejaré!". Lo que es aún mejor es que la clase Executors proporciona un conjunto de métodos que pueden crear grupos de subprocesos y ejecutores completamente configurados. Usaremos newFixedThreadPool, que crea una cantidad predefinida de subprocesos y no permite que la cantidad de subprocesos exceda este valor predefinido.

Esto significa que si se utilizan todos los subprocesos, el comando enviado se colocará en una cola de espera, por supuesto, esto lo administra el ejecutor; Además, está el ExecutorService que gestiona el ciclo de vida del ejecutor y el CompletionService que abstrae más detalles y actúa como una cola de tareas completadas.

Si necesita un control preciso sobre la cantidad de subprocesos generados por su programa, así como su comportamiento preciso, entonces los ejecutores y los servicios de ejecutor serán la opción correcta. Por ejemplo, una pregunta importante a considerar detenidamente es ¿qué estrategia se necesita cuando todos los hilos están ocupados haciendo otras cosas?

¿Aumentar el número de hilos o limitar el número? ¿Poner la tarea en la cola y esperar? ¿Qué pasa si la cola también está llena? ¿Aumentar el tamaño de la cola sin límite?

Gracias al JDK, ya existen muchos elementos de configuración que responden a estas preguntas y tienen nombres intuitivos, como el anterior

Executors.newFixedThreadPool (4).

El ciclo de vida de subprocesos y servicios también se puede configurar mediante opciones para que los recursos se puedan cerrar en el momento adecuado. El único inconveniente es que las opciones de configuración podrían ser un poco más sencillas e intuitivas para los novatos. Sin embargo, cuando se trata de programación concurrente, difícilmente puede resultar más sencillo. En resumen, para sistemas grandes, lo más apropiado es utilizar ejecutores.

3. Framework ForkJoin

A través de flujos paralelos y usando ForkJoinPool (FJP), se han agregado flujos paralelos a Java 8. A partir de entonces, tenemos un método simple para procesar colecciones. paralelo. Junto con lambda, forma una poderosa herramienta para la computación concurrente.

Si planeas utilizar este método, hay algunas cosas que debes tener en cuenta. Primero, debes comprender algunos conceptos de programación funcional, que en realidad es más ventajoso. En segundo lugar, es difícil saber si una secuencia paralela realmente utiliza más de un subproceso; esto depende de la implementación específica de la secuencia. Si no controla la fuente de datos de una transmisión, no puede estar seguro de lo que hace.

Además, debes recordar que el paralelismo se logra de forma predeterminada a través de ForkJoinPool.commonPool. Este grupo general lo gestiona la JVM y lo comparten todos los subprocesos del proceso de la JVM.

Esto simplifica los elementos de configuración para que no tengas que preocuparte por ello.

ForkJoin es un excelente marco y es la primera opción cuando necesitas escribir un pequeño programa que implica procesamiento paralelo. Su mayor desventaja es que hay que anticiparse a posibles complicaciones. Esto es difícil de lograr sin un conocimiento profundo de la JVM en su conjunto. Esto sólo puede surgir de la experiencia.

4. Modelo de actor

No hay ninguna implementación de actor en el JDK; por lo tanto, debes hacer referencia a algunas bibliotecas que implementan actores.

En resumen, en el modelo de actor, tratas todo como un actor. Un actor es una entidad computacional, como el hilo del primer ejemplo anterior, que puede recibir mensajes de otros actores porque todo es un actor.

Al responder a los mensajes, puede enviar mensajes a otros actores, o crear nuevos actores e interactuar con ellos, o simplemente cambiar su propio estado interno. Bastante simple, pero es un concepto muy poderoso. Su marco administra el ciclo de vida y la mensajería, solo necesita especificar cuál es la unidad de cálculo.

Además, el modelo de actor hace hincapié en evitar el estado global, lo que aportará mucha comodidad. Puede aplicar estrategias de supervisión como reintentos gratuitos, diseños de sistemas distribuidos más simples, tolerancia a errores y más. Akka Actors tiene una interfaz Java y es una de las bibliotecas JVM Actor más populares.

De hecho, también tiene una interfaz Scala y es la biblioteca de actores predeterminada actual de Scala. Scala solía implementar actores internamente. Muchos lenguajes JVM han implementado actores, como Future. Estos ilustran que el modelo Actor ha sido ampliamente aceptado y se considera una adición muy valiosa al lenguaje.

Los actores de Akka utilizan el marco ForkJoin internamente para manejar el trabajo. El poder del modelo Actor proviene de la interfaz del objeto Props, a través del cual podemos definir modos de selección específicos, direcciones de correo electrónico personalizadas, etc. para los actores. El sistema resultante también es configurable y contiene sólo unas pocas piezas móviles.

¡Esto es una buena señal! Una desventaja de utilizar el modelo Actor es que requiere que se evite el estado global, por lo que hay que diseñar la aplicación con cuidado, lo que puede complicar la migración del proyecto. Al mismo tiempo, también tiene muchas ventajas, por lo que vale la pena aprender algunos paradigmas nuevos y utilizar nuevas bibliotecas.

Se puede ver que Scala es muy simple. Con sus subprocesos concurrentes, no tiene que lidiar con subprocesos, bloqueos, comunicación entre subprocesos, colaboración entre subprocesos y otros problemas. Encapsula todo esto.