Problemas resueltos por multihilo
En realidad, los subprocesos múltiples se inventaron para lograr dos objetivos importantes:-
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!
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 :)
-
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.
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:
- 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.
- 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.
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. 
GO TO FULL VERSION