CodeGym /Blog Java /Random-ES /Multiproceso en Java
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Multiproceso en Java

Publicado en el grupo Random-ES
¡Hola! En primer lugar, felicidades: ¡has llegado al tema de Multithreading en Java! Este es un logro serio: has recorrido un largo camino. Pero prepárate: este es uno de los temas más difíciles del curso. Y no es que estemos usando clases complejas o muchos métodos aquí: de hecho, usaremos menos de veinte. Es más que necesitarás cambiar un poco tu forma de pensar. Anteriormente, sus programas se ejecutaban secuencialmente. Algunas líneas de código vinieron después de otras, algunos métodos vinieron después de otros y todo estaba básicamente claro. Primero, calculamos algo, luego mostramos el resultado en la consola y luego el programa finalizó. Para comprender los subprocesos múltiples, es mejor pensar en términos de paralelismo. Comencemos con algo bastante simple: ) Imagina que tu familia se está mudando de una casa a otra. Reunir todos sus libros será una parte importante de la mudanza. Has acumulado muchos libros y necesitas ponerlos en cajas. Actualmente, eres el único disponible. Mamá está preparando comida, el hermano está empacando ropa y la hermana fue a la tienda. Solo, puedes arreglártelas de alguna manera. Tarde o temprano, completará la tarea usted mismo, pero le llevará mucho tiempo. Sin embargo, tu hermana regresará de la tienda en 20 minutos y no tiene nada más que hacer. Para que ella pueda unirse a ti. La tarea no ha cambiado: poner libros en cajas. Pero se está realizando el doble de rápido. ¿Por qué? Porque el trabajo está ocurriendo en paralelo. Dos 'hilos' diferentes (tú y tu hermana) están realizando la misma tarea simultáneamente. Y si nada cambia, entonces habrá una gran diferencia de tiempo en comparación con la situación en la que haces todo por ti mismo. Si el hermano termina su trabajo pronto, puede ayudarte y las cosas irán aún más rápido.

Problemas resueltos por multihilo

En realidad, los subprocesos múltiples se inventaron para lograr dos objetivos importantes:
  1. Haz varias cosas al mismo tiempo.

    En el ejemplo anterior, diferentes subprocesos (miembros de la familia) realizaron varias acciones en paralelo: lavaron los platos, fueron a la tienda y empacaron cosas.

    Podemos ofrecer un ejemplo más relacionado con la programación. Suponga que tiene un programa con una interfaz de usuario. Al hacer clic en 'Continuar' en el programa, deberían realizarse algunos cálculos y el usuario debería ver la siguiente pantalla. Si estas acciones se realizaran secuencialmente, el programa simplemente se colgaría después de que el usuario haga clic en el botón 'Continuar'. El usuario verá la pantalla con el botón 'Continuar' hasta que el programa realice todos los cálculos internos y llegue a la parte donde se actualiza la interfaz de usuario.

    Bueno, ¡supongo que esperaremos un par de minutos!

    Multithreading en Java: qué es, sus ventajas y sus inconvenientes comunes - 3

    O podríamos reelaborar nuestro programa o, como dicen los programadores, 'ponerlo en paralelo'. Realicemos nuestros cálculos en un subproceso y dibujemos la interfaz de usuario en otro. La mayoría de las computadoras tienen suficientes recursos para hacer esto. Si tomamos esta ruta, entonces el programa no se bloqueará y el usuario se moverá sin problemas entre las pantallas sin preocuparse por lo que sucede dentro. Uno no interfiere con el otro :)

  2. Realice los cálculos más rápidamente.

    Todo es mucho más simple aquí. Si nuestro procesador tiene múltiples núcleos, y la mayoría de los procesadores actuales los tienen, entonces varios núcleos pueden manejar nuestra lista de tareas en paralelo. Obviamente, si necesitamos realizar 1000 tareas y cada una toma un segundo, un núcleo puede terminar la lista en 1000 segundos, dos núcleos en 500 segundos, tres en un poco más de 333 segundos, etc.

Pero como ya ha leído en esta lección, los sistemas actuales son muy inteligentes e incluso en un solo núcleo de computación pueden lograr paralelismo, o más bien pseudoparalelismo, donde las tareas se realizan alternativamente. Pasemos de las generalidades a los detalles y conozcamos la clase más importante en la biblioteca de subprocesos múltiples de Java: java.lang.Thread. Estrictamente hablando, los subprocesos de Java están representados por instancias de la clase Thread . Esto significa que para crear y ejecutar 10 subprocesos, necesita 10 instancias de esta clase. Escribamos el ejemplo más simple:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Para crear y ejecutar subprocesos, necesitamos crear una clase, hacer que herede java.lang . Subproceso de clase y anula su método run() . Este último requisito es muy importante. Es en el método run() donde definimos la lógica para que se ejecute nuestro subproceso. Ahora, si creamos y ejecutamos una instancia de MyFirstThread , el método run() mostrará una línea con un nombre: el método getName() muestra el nombre del 'sistema' del hilo, que se asigna automáticamente. Pero, ¿por qué estamos hablando tentativamente? ¡Creemos uno y descubramos!

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Salida de la consola: ¡Soy Thread! Mi nombre es Thread-2 ¡Soy Thread! Mi nombre es Thread-1 ¡Soy Thread! Mi nombre es Thread-0 ¡Soy Thread! Mi nombre es Thread-3 ¡Soy Thread! Mi nombre es Thread-6 ¡Soy Thread! Mi nombre es Thread-7 ¡Soy Thread! Mi nombre es Thread-4 ¡Soy Thread! Mi nombre es Thread-5 ¡Soy Thread! Mi nombre es Thread-9 ¡Soy Thread! Mi nombre es Thread-8. Vamos a crear 10 hilos ( objetos MyFirstThread , que heredan Thread ) e iniciarlos llamando al método start() en cada objeto. Después de llamar al método start() , se ejecuta la lógica del método run() . Nota: los nombres de los hilos no están en orden. Es extraño que no fueran secuencialmente:, Subproceso-1 , Subproceso-2 , etc.? Da la casualidad de que este es un ejemplo de un momento en que el pensamiento 'secuencial' no encaja. El problema es que solo proporcionamos comandos para crear y ejecutar 10 subprocesos. El planificador de subprocesos, un mecanismo especial del sistema operativo, decide su orden de ejecución. Su diseño preciso y la estrategia de toma de decisiones son temas para una discusión profunda en la que no nos sumergiremos ahora. Lo principal a recordar es que el programador no puede controlar el orden de ejecución de los hilos. Para comprender la gravedad de la situación, intente ejecutar el método main() en el ejemplo anterior un par de veces más. Salida de la consola en la segunda ejecución: ¡Soy Hilo! Mi nombre es Thread-0 ¡Soy Thread! Mi nombre es Thread-4 ¡Soy Thread! Mi nombre es Thread-3 ¡Soy Thread! Mi nombre es Thread-2 ¡Soy Thread! Mi nombre es Thread-1 ¡Soy Thread! Mi nombre es Thread-5 ¡Soy Thread! Mi nombre es Thread-6 ¡Soy Thread! Mi nombre es Thread-8 ¡Soy Thread! Mi nombre es Thread-9 ¡Soy Thread! Mi nombre es Thread-7 Salida de la consola de la tercera ejecución: ¡ Soy Thread! Mi nombre es Thread-0 ¡Soy Thread! Mi nombre es Thread-3 ¡Soy Thread! Mi nombre es Thread-1 ¡Soy Thread! Mi nombre es Thread-2 ¡Soy Thread! Mi nombre es Thread-6 ¡Soy Thread! Mi nombre es Thread-4 ¡Soy Thread! Mi nombre es Thread-9 ¡Soy Thread! Mi nombre es Thread-5 ¡Soy Thread! Mi nombre es Thread-7 ¡Soy Thread! Mi nombre es Thread-8

Problemas creados por subprocesos múltiples

En nuestro ejemplo con los libros, vio que los subprocesos múltiples resuelven tareas muy importantes y pueden hacer que nuestros programas sean más rápidos. A menudo, muchas veces más rápido. Pero los subprocesos múltiples se consideran un tema difícil. De hecho, si se usa incorrectamente, crea problemas en lugar de resolverlos. Cuando digo 'crea problemas', no me refiero a un sentido abstracto. Hay dos problemas específicos que pueden crear los subprocesos múltiples: interbloqueo y condiciones de carrera. Interbloqueo es una situación en la que varios subprocesos están esperando los recursos que tienen entre sí, y ninguno de ellos puede continuar ejecutándose. Hablaremos más al respecto en lecciones posteriores. El siguiente ejemplo será suficiente por ahora: Multithreading en Java: qué es, sus ventajas y sus inconvenientes comunes - 4imagine que Thread-1 interactúa con algún Object-1 y que Thread-2 interactúa con Object-2. Además, el programa está escrito de manera que:
  1. Thread-1 deja de interactuar con Object-1 y cambia a Object-2 tan pronto como Thread-2 deja de interactuar con Object-2 y cambia a Object-1.
  2. Thread-2 deja de interactuar con Object-2 y cambia a Object-1 tan pronto como Thread-1 deja de interactuar con Object-1 y cambia a Object-2.
Incluso sin una comprensión profunda de los subprocesos múltiples, puede ver fácilmente que no sucederá nada. Los hilos nunca cambiarán de lugar y esperarán el uno al otro para siempre. El error parece obvio, pero en realidad no lo es. Puedes hacer esto fácilmente en un programa. Consideraremos ejemplos de código que provoca interbloqueos en lecciones posteriores. Por cierto, Quora tiene un excelente ejemplo de la vida real que explica qué punto muertoes. 'En algunos estados de la India, no te venderán tierras agrícolas a menos que seas un agricultor registrado. Sin embargo, no te registrarán como agricultor si no posees tierras agrícolas'. ¡Excelente! ¡¿Qué podemos decir?! :) Ahora hablemos de las condiciones de carrera. Una condición de carrera es un error de diseño en un sistema o aplicación de subprocesos múltiples, donde la operación del sistema o aplicación depende del orden en que se ejecutan las partes del código. Recuerde, nuestro ejemplo donde comenzamos hilos:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
¡Ahora imagine que el programa es responsable de hacer funcionar un robot que cocina la comida! Thread-0 saca los huevos de la nevera. Thread-1 enciende la estufa. Thread-2 toma una sartén y la pone en la estufa. Thread-3 enciende la estufa. Thread-4 vierte aceite en la sartén. Thread-5 rompe los huevos y los vierte en la sartén. Thread-6 arroja las cáscaras de huevo a la basura. Thread-7 retira los huevos cocidos del quemador. Thread-8 pone los huevos cocidos en un plato. Thread-9 lava los platos. Mire los resultados de nuestro programa: Hilo ejecutado: Hilo-0 Hilo ejecutado: Hilo-2 Hilo ejecutado Hilo-1 Hilo ejecutado: Hilo-4 Hilo ejecutado: Hilo-9 Hilo ejecutado: Hilo-5 Hilo ejecutado: Hilo-8 Hilo ejecutado: Subproceso-7 Subproceso ejecutado: Subproceso-3 ¿Es esta una rutina de comedia? :) Y todo porque el trabajo de nuestro programa depende del orden de ejecución de los hilos. Dada la más mínima violación de la secuencia requerida, nuestra cocina se convierte en un infierno y un robot loco destruye todo a su alrededor. Este también es un problema común en la programación multiproceso. Lo escucharás más de una vez. Para concluir esta lección, me gustaría recomendar un libro sobre subprocesos múltiples. Multithreading en Java: qué es, sus ventajas y sus inconvenientes comunes - 6'Java Concurrency in Practice' se escribió en 2006, pero no ha perdido su relevancia. Está dedicado a la programación Java multiproceso, desde lo básico hasta los errores y antipatrones más comunes. Si algún día decide convertirse en un gurú de subprocesos múltiples, este libro es una lectura obligada. ¡Nos vemos en las próximas lecciones! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION