Problemas que resuelve el multithreading en Java
Básicamente, el subproceso múltiple de Java se inventó para resolver dos problemas principales:-
Realiza múltiples acciones al mismo tiempo.
En el ejemplo anterior, diferentes hilos (es decir, miembros de la familia) realizaron varias acciones en paralelo: lavaron los platos, fueron a la tienda, doblaron cosas.
Se puede dar un ejemplo más de "programador". Imagine que tiene un programa con una interfaz de usuario. Cuando se hace clic en el botón Continuar, deberían realizarse algunos cálculos dentro del programa y el usuario debería ver la siguiente pantalla de interfaz. Si estas acciones se llevan a cabo secuencialmente, después de hacer clic en el botón "Continuar", el programa simplemente se congelará. El usuario verá la misma pantalla con un botón “Continuar” hasta que se completen todos los cálculos internos y el programa llegue a la parte donde se comenzará a dibujar la interfaz.
Bueno, ¡esperemos un par de minutos!
También podemos rehacer nuestro programa o, como dicen los programadores, "paralelizarlo". Deje que los cálculos necesarios se realicen en un hilo y la representación de la interfaz en otro. La mayoría de las computadoras tienen suficientes recursos para esto. En este caso, el programa no será "estúpido" y el usuario se moverá tranquilamente entre las pantallas de la interfaz, sin preocuparse por lo que sucede en el interior. No interfiere :)
-
Acelera los cálculos.
Aquí todo es mucho más sencillo. Si nuestro procesador tiene varios núcleos, y la mayoría de procesadores ahora son multinúcleo, nuestra lista de tareas se puede resolver en paralelo con varios núcleos. Obviamente, si necesitamos resolver 1000 problemas y cada uno de ellos se resuelve en un segundo, un núcleo resolverá la lista en 1000 segundos, dos núcleos en 500 segundos, tres en poco más de 333 segundos, y así sucesivamente.
Thread
. Es decir, para crear y ejecutar 10 subprocesos, necesitará 10 objetos 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 lanzar subprocesos, necesitamos crear una clase y heredarla del archivo java.lang
. Thread
y anular el método en él run()
. El último es muy importante. Es en el método donde run()
prescribimos la lógica que nuestro hilo debe ejecutar. Ahora, si creamos una instancia MyFirstThread
y la ejecutamos, el método run()
imprimirá una línea con su nombre en la consola: el método getName()
imprime el nombre “sistema” del hilo, que se asigna automáticamente. Aunque, en realidad, ¿por qué “si”? ¡Creemos y probemos!
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 Creamos 10 subprocesos (objetos) MyFirstThread
que heredan Thread
y los lanzan llamando al método del objeto start()
. Después de llamar a un método , start()
su método comienza a funcionar run()
y se ejecuta la lógica escrita en él. Tenga en cuenta: los nombres de los hilos no están en orden. Es bastante extraño, ¿por qué no fueron ejecutados por turnos: Thread-0
, Thread-1
, Thread-2
etc.? Este es exactamente un ejemplo de cuando el pensamiento estándar y “secuencial” no funciona. El hecho es que en este caso solo emitimos comandos para crear y ejecutar 10 hilos. El orden en que deben iniciarse lo decide el programador de subprocesos: un mecanismo especial dentro del sistema operativo. Cómo está exactamente estructurado y según qué principio toma decisiones es un tema muy complejo y no profundizaremos en él ahora. Lo principal a recordar es que el programador no puede controlar la secuencia de ejecución del hilo. Para darse cuenta de la gravedad de la situación, intente ejecutar el método main()
del ejemplo anterior un par de veces más. Segunda salida de la consola: ¡ Soy Thread! 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 Tercera salida de consola: ¡ 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 Hilo-8
Problemas que crea el subproceso múltiple
En el ejemplo de los libros, viste que el subproceso múltiple resuelve problemas bastante importantes y su uso acelera el trabajo de nuestros programas. En muchos casos, muchas veces. Pero no en vano el multithreading se considera un tema complejo. Después de todo, si se usa incorrectamente, crea problemas en lugar de resolverlos. Cuando digo "crear problemas", no me refiero a algo abstracto. Hay dos problemas específicos que pueden causar los subprocesos múltiples: interbloqueo y condición de carrera. Un punto muerto es una situación en la que varios subprocesos esperan recursos ocupados entre sí y ninguno de ellos puede continuar ejecutándose. Hablaremos más sobre esto en futuras conferencias, pero por ahora este ejemplo será suficiente: imagine que el subproceso 1 está trabajando con algún Objeto-1 y el subproceso 2 está trabajando con el Objeto-2. El programa está escrito así:- Thread-1 dejará de funcionar con el Objeto-1 y cambiará al Objeto-2 tan pronto como Thread-2 deje de funcionar con el Objeto 2 y cambie al Objeto-1.
- Thread-2 dejará de funcionar con el Objeto-2 y cambiará al Objeto-1 tan pronto como el Hilo-1 deje de funcionar con el Objeto 1 y cambie al Objeto-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Выполнен поток " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
¡Ahora imagina que el programa es responsable del funcionamiento de un robot que prepara comida! Thread-0 saca los huevos del frigorífico. El flujo 1 enciende la estufa. Stream-2 saca una sartén y la pone al fuego. El arroyo 3 enciende un fuego en la estufa. El chorro 4 vierte aceite en la sartén. El chorro 5 rompe los huevos y los vierte en la sartén. El arroyo 6 tira las conchas a la basura. Stream-7 retira los huevos revueltos terminados del fuego. Potok-8 pone huevos revueltos en un plato. El arroyo 9 lava los platos. Mire los resultados de nuestro programa: Hilo-0 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 Hilo-7 hilo ejecutado Hilo-7 hilo ejecutado -3 hilo-6 hilo ejecutado ¿ Es divertido el script? :) Y todo porque el funcionamiento de nuestro programa depende del orden en que se ejecutan los hilos. A la más mínima violación de la secuencia, nuestra cocina se convierte en un infierno y un robot enloquecido destruye todo lo que la rodea. Este también es un problema común en la programación multiproceso, del que escuchará más de una vez. Al final de la conferencia, me gustaría recomendarles un libro sobre subprocesos múltiples.
GO TO FULL VERSION