Threading se refiere a la práctica de ejecutar procesos de programación simultáneamente para mejorar el rendimiento de la aplicación. Si bien no es tan común trabajar con subprocesos directamente en aplicaciones comerciales, se usan todo el tiempo en los marcos de Java. Por ejemplo, los marcos que procesan un gran volumen de información utilizan subprocesos para gestionar los datos. La manipulación simultánea de subprocesos o procesos de CPU mejora el rendimiento, lo que da como resultado programas más rápidos y eficientes. Este artículo le presenta algunos conceptos básicos de los subprocesos Java tradicionales y la ejecución de subprocesos en la máquina virtual Java. Consulte la introducción de InfoWorld a Project Loom para obtener más información sobre los subprocesos virtuales y el nuevo modelo de concurrencia estructurada de Java. Encuentre su primer subproceso: el método main() de Java. Incluso si nunca ha trabajado directamente con subprocesos de Java, ha trabajado indirectamente con ellos porque el método principal de Java () El método contiene un hilo principal. Cada vez que ejecuta el método main(), también ejecuta el Thread principal. Estudiar la clase Thread es muy útil para comprender cómo funcionan los threads en programas Java. Podemos acceder al hilo que se está ejecutando invocando el método currentThread().getName(), como se muestra aquí: public class MainThread { public static void main(String… mainThread) { System.out.println(Thread.currentThread ().getName()); } } Este código imprimirá «principal», identificando el hilo que se está ejecutando actualmente. Saber cómo identificar el subproceso que se está ejecutando es el primer paso para absorber los conceptos de los subprocesos. El ciclo de vida de los subprocesos de Java Cuando se trabaja con subprocesos, es fundamental estar consciente del estado de los subprocesos. El ciclo de vida del subproceso Java consta de seis estados del subproceso: Nuevo: se ha creado una instancia de un nuevo Thread(). Ejecutable: se ha invocado el método start() del hilo. En ejecución: se ha invocado el método start() y el hilo se está ejecutando. Suspendido: el hilo se suspende temporalmente y otro hilo puede reanudarlo. Bloqueado: el hilo está esperando una oportunidad para ejecutarse. Esto sucede cuando un hilo ya ha invocado el método sincronizado() y el siguiente hilo debe esperar hasta que finalice. Terminado: la ejecución del hilo está completa. Rafael Chinelato Del Nero Figura 1. Los seis estados del ciclo de vida de los subprocesos de Java Hay más que explorar y comprender sobre los estados de los subprocesos, pero la información de la Figura 1 es suficiente por ahora. Ampliación de una clase Thread En su forma más simple, el procesamiento concurrente se realiza extendiendo una Clase de subproceso, como se muestra aquí: clase pública InheritingThread extends Thread { InheritingThread(String threadName) { super(threadName); } public static void main(String… heredando) { System.out.println(Thread.currentThread().getName() + » se está ejecutando»); new InheritingThread(«heredandoThread»).start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + » se está ejecutando»); } } Aquí, estamos ejecutando dos subprocesos: MainThread y InheritingThread. Cuando invocamos el método start() con el nuevo heredingThread(), se ejecuta la lógica en el método run(). También pasamos el nombre del segundo hilo en el constructor de la clase Thread, por lo que la salida será: main is running . heredarThread se está ejecutando. La interfaz Runnable En lugar de utilizar la herencia, puede implementar la interfaz Runnable. Pasar Runnable dentro de un constructor de subprocesos da como resultado menos acoplamiento y más flexibilidad. Después de pasar Runnable, podemos invocar el método start() exactamente como lo hicimos en el ejemplo anterior: public class RunnableThread implements Runnable { public static void main(String… runnableThread) { System.out.println(Thread.currentThread() .getName()); nuevo hilo(nuevo RunnableThread()).start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } Hilos no demonio vs. demonio En términos de ejecución, hay dos tipos de subprocesos: Los subprocesos no demonio se ejecutan hasta el final. El hilo principal es un buen ejemplo de un hilo que no es un demonio. El código en main() siempre se ejecutará hasta el final, a menos que System.exit() fuerce la finalización del programa. Un hilo de demonio es todo lo contrario, básicamente un proceso que no es necesario ejecutar hasta el final. Recuerde la regla: si un subproceso no demonio finaliza antes que un subproceso demonio, el subproceso demonio no se ejecutará hasta el final. Para comprender mejor la relación entre los subprocesos demonio y no demonio, estudie este ejemplo: import java.util .stream.IntStream; public class NonDaemonAndDaemonThread { public static void main(String… nonDaemonAndDaemon) throws InterruptedException { System.out.println(«Iniciando la ejecución en el hilo » + Thread.currentThread().getName()); Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100000) .forEach(System.out::println)); daemonThread.setDaemon(verdadero); daemonThread.start(); Hilo.dormir(10); System.out.println(«Fin de la ejecución en el Thread » + Thread.currentThread().getName()); } } En este ejemplo he usado un hilo de demonio para declarar un rango de 1 a 100.000, iterarlos todos y luego imprimir. Pero recuerde, un hilo de demonio no completará la ejecución si el hilo principal del no demonio termina primero. La salida procederá de la siguiente manera: Inicio de la ejecución en el hilo principal. Imprima números del 1 al posiblemente 100.000. Fin de la ejecución en el hilo principal, muy probablemente antes de que se complete la iteración hasta 100.000. El resultado final dependerá de su implementación de JVM. Como puede ver, los subprocesos son impredecibles. Prioridad de subprocesos y JVMEs posible priorizar la ejecución de subprocesos con el método setPriority, pero, nuevamente, cómo se maneja depende de la implementación de JVM. Linux, macOS y Windows tienen diferentes implementaciones de JVM y cada uno manejará la prioridad de los subprocesos de acuerdo con los valores predeterminados. Sin embargo, la prioridad de los subprocesos que establezca influye en el orden de invocación de los subprocesos. Las tres constantes declaradas en la clase Thread son: /** * La prioridad mínima que puede tener un hilo. */ public static final int MIN_PRIORITY = 1; /** * La prioridad predeterminada que se asigna a un subproceso. */ public static final int NORM_PRIORITY = 5; /** * La máxima prioridad que puede tener un hilo. */ public static final int MAX_PRIORITY = 10; Intente ejecutar pruebas en el siguiente código para ver con qué prioridad de ejecución termina: public class ThreadPriority { public static void main(String… threadPriority) { Thread moeThread = new Thread(() -> System.out.println(» Moé»)); Hilo barneyThread = new Thread(() -> System.out.println(«Barney»)); Hilo homerThread = new Thread(() -> System.out.println(«Homero»)); moeThread.setPriority(Thread.MAX_PRIORITY); barneyThread.setPriority(Thread.NORM_PRIORITY); homerThread.setPriority(Thread.MIN_PRIORITY); homerThread.start(); barneyThread.start(); moeThread.start(); } } Incluso si configuramos moeThread como MAX_PRIORITY, no podemos contar con que este hilo se ejecute primero. En cambio, el orden de ejecución será aleatorio. Una nota sobre constantes y enumeraciones La clase Thread se introdujo con la primera versión de Java. En ese momento, las prioridades se establecían mediante constantes, no enumeraciones. Sin embargo, existe un problema con el uso de constantes: si pasamos un número de prioridad que no está en el rango de 1 a 10, el método setPriority() generará una IllegalArgumentException. Hoy, podemos usar enumeraciones para solucionar este problema. El uso de enumeraciones hace que sea imposible pasar un argumento ilegal, lo que simplifica el código y nos brinda más control sobre su ejecución. Qué recordar sobre los subprocesos de Java Invoque el método start() para iniciar un subproceso. Es posible ampliar la clase Thread directamente para utilizar hilos. Es posible implementar una acción de subproceso dentro de una interfaz Runnable. La prioridad de los subprocesos depende de la implementación de JVM. El comportamiento de los subprocesos también depende de la implementación de JVM. Un hilo de demonio no se completará si un hilo adjunto que no es de demonio termina primero. Errores comunes con subprocesos de Java Invocar el método run() no es la forma de iniciar un nuevo subproceso. Intentar iniciar un hilo dos veces provocará una IllegalThreadStateException. Evite permitir que múltiples procesos cambien el estado de un objeto. No escriba una lógica de programa que dependa de la prioridad del subproceso (no puede predecirla). No confíe en el orden de ejecución del subproceso; incluso si inicia un subproceso primero, no hay garantía de que se ejecute primero. ¡Acepte el desafío de los subprocesos de Java! Ha aprendido solo algunas cosas sobre los subprocesos de Java, así que intentemos un desafío de Java para probar lo que ha aprendido. clase pública ThreadChallenge {privado estático int wolverineAdrenaline = 10; public static void main(String… doYourBest) { nueva motocicleta(«Harley Davidson»).start(); Motocicleta fastBike = motocicleta nueva («Dodge Tomahawk»); fastBike.setPriority(Thread.MAX_PRIORITY); fastBike.setDaemon (falso); fastBike.start(); Motocicleta yamaha = motocicleta nueva(«Yamaha YZF»); yamaha.setPriority(Thread.MIN_PRIORITY); yamaha.start(); } clase estática Motocicleta extiende Thread { Motocicleta(String bikeName) { super(bikeName); } @Override public void run() { wolverineAdrenaline++; if (wolverineAdrenaline == 13) { System.out.println(this.getName()); } } } } ¿Cuál crees que será el resultado de este código? Estas son las opciones:A. Harley Davidson B. Esquivar TomahawkC. Yamaha YZFD. IndeterminadoResolviendo el desafíoEn el código anterior, creamos tres hilos. El primer hilo es Harley Davidson y le asignamos a este hilo la prioridad predeterminada. El segundo hilo es Dodge Tomahawk, al que se le ha asignado MAX_PRIORITY. La tercera es Yamaha YZF, con MIN_PRIORITY. Luego iniciamos los subprocesos. Para determinar el orden en que se ejecutarán los subprocesos, primero puede observar que la clase Motorcycle extiende la clase Thread y que hemos pasado el nombre del subproceso en el constructor. También anulamos el método run() con una condición: if (wolverineAdrenaline == 13). Aunque Yamaha YZF es el tercer subproceso en nuestro orden de ejecución y tiene MIN_PRIORITY, no hay garantía de que se ejecute en último lugar durante todas las implementaciones de JVM. También puede observar que en este ejemplo configuramos el subproceso Dodge Tomahawk como demonio. Debido a que es un hilo demoníaco, es posible que Dodge Tomahawk nunca complete la ejecución. Pero los otros dos subprocesos no son demonios por defecto, por lo que los subprocesos Harley Davidson y Yamaha YZF definitivamente completarán su ejecución. Para concluir, el resultado será D: Indeterminado. Esto se debe a que no hay garantía de que el programador de subprocesos siga nuestro orden de ejecución o prioridad de subprocesos. Recuerde, no podemos confiar en la lógica del programa (orden de subprocesos o prioridad de subprocesos) para predecir el orden de ejecución de la JVM. ¡Desafío de video! Depurar argumentos variables La depuración es una de las formas más sencillas de absorber completamente los conceptos de programación y al mismo tiempo mejorar el código. En este video puedes seguir mientras depuro y explico el desafío del comportamiento del hilo: Obtenga más información sobre Java Copyright © 2024 IDG Communications, Inc.