English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Java proporciona soporte integrado para la programación de hilos múltiples. Un hilo es una secuencia de control de flujo única en un proceso, en un proceso se pueden ejecutar varios hilos de manera concurrente, cada hilo ejecuta diferentes tareas en paralelo.
La multihilos es una forma especial de multitarea, pero utiliza un costo de recursos menor.
Aquí se define otro término relacionado con los hilos - Un proceso: Un proceso incluye el espacio de memoria asignado por el sistema operativo, que contiene una o más hilos. Un hilo no puede existir por sí solo, debe ser parte de un proceso. Un proceso sigue ejecutándose hasta que todos los hilos no guardianes hayan finalizado su ejecución.
Los hilos multiconcurrentes pueden permitir a los programadores escribir programas de alta eficiencia para aprovechar al máximo el CPU.
Un hilo es un proceso de ejecución dinámica, también tiene un proceso de nacimiento a muerte.
La siguiente imagen muestra el ciclo de vida completo de un hilo.
Estado de nuevo:
usar new la palabra clave y Thread Después de que una clase o su subclase crea un objeto de hilo, el objeto de hilo está en estado de nuevo. Mantiene este estado hasta que el programa start() Este hilo.
Estado de listo:
Después de que el objeto de hilo llama al método start(), el hilo entra en estado de listo. Los hilos en estado de listo están en la cola de listo, esperando la programación del administrador de hilos en JVM.
Estado de ejecución:
Si el hilo listo obtiene recursos de CPU, puede ejecutar run()En ese momento, el hilo está en estado de ejecución. Los hilos en estado de ejecución son los más complejos, pueden cambiar a estado de bloqueo, listo y de muerte.
Estado de bloqueo:
Si un hilo ejecuta métodos como sleep(dormir)、suspend(suspender)y pierde los recursos ocupados, el hilo pasa del estado de ejecución al estado de bloqueo. Puede regresar al estado de listo después de que se complete el tiempo de sueño o se obtengan los recursos de dispositivo. Se puede dividir en tres tipos:
Bloqueo de espera: el hilo en estado de ejecución ejecuta el método wait(), lo que hace que el hilo entre en estado de bloqueo de espera.
Bloqueo de sincronización: el hilo falla al obtener el bloqueo synchronized (porque el bloqueo está ocupado por otro hilo).
Otro bloqueo: al llamar a sleep() o join() del hilo, se emitió I/O se solicita, el hilo entra en estado de bloqueo. Cuando el estado de sleep() expira, join() espera que el hilo termine o expira, o I/O el trabajo se completa, el hilo vuelve al estado de listo.
Estado de muerte:
Un hilo en estado de ejecución completa una tarea o se produce alguna condición de terminación, el hilo cambia a estado de terminación.
Cada hilo en Java tiene una prioridad, lo que ayuda al sistema operativo a determinar el orden de programación de los hilos.
La prioridad de un hilo en Java es un entero, con un rango de valores 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )
Por defecto, cada hilo se asigna una prioridad NORM_PRIORITY(5)。
Las tareas en hilos con alta prioridad son más importantes para el programa y deben asignarse recursos de procesador antes de los hilos de baja prioridad. Sin embargo, la prioridad del hilo no garantiza el orden de ejecución del hilo y depende mucho de la plataforma.
Java proporciona tres métodos para crear hilos:
Crear hilos mediante la implementación de la interfaz Runnable;
Crear hilos mediante la herencia de la clase Thread en sí misma;
Crear hilos mediante Callable y Future
El método más simple de crear un hilo es crear una clase que implements la interfaz Runnable.
Para implementar Runnable, una clase solo necesita ejecutar una llamada a un método run(), declarado como follows:
public void run()
Puede sobrescribir este método, lo importante es entender que run() puede llamar a otros métodos, usar otras clases y declarar variables, al igual que el hilo principal.
Después de crear una clase que implementa la interfaz Runnable, puede instanciar un objeto de hilo en la clase.
Thread definió varios métodos de construcción, el siguiente es uno de los más utilizados:
Thread(Runnable threadOb,String threadName);
Aquí, threadOb es una instancia de una clase que implementa la interfaz Runnable, y threadName especifica el nombre del nuevo hilo.
Después de crear el hilo, debe llamar al método start() para que se ejecute.
void start();
A continuación, se muestra un ejemplo de cómo crear un hilo y comenzar a ejecutarlo:
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println("Creando ")} + threadName); } public void run() { System.out.println("Ejecutando ", + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Haga que la hebra duerma un tiempo Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrumpido."); } System.out.println("Thread " + threadName + " exiting."); } public void start() { System.out.println("Starting " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); } } } public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); } }
El resultado de compilar y ejecutar el programa anterior es el siguiente:
Creando Thread-1 Thread comenzando-1 Creando Thread-2 Thread comenzando-2 Thread en ejecución-1 Thread: Thread-1, 4 Thread en ejecución-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Segunda manera de crear un hilo: crear una nueva clase que herede de la clase Thread, y luego crear una instancia de esa clase.
La clase heredada debe sobrescribir el método run(), que es el punto de entrada del nuevo hilo. También debe llamar al método start() para ejecutarse.
Esta méthode, aunque se enumera como un modo de implementación de hilos múltiples, es esencialmente un ejemplo que implementa la interfaz Runnable.
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creando ")} + threadName); } public void run() { System.out.println("Ejecutando ", + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Haga que la hebra duerma un tiempo Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrumpido."); } System.out.println("Thread " + threadName + " exiting."); } public void start() { System.out.println("Starting " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo("Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo("Thread-2"); T2.start(); } }
El resultado de compilar y ejecutar el programa anterior es el siguiente:
Creando Thread-1 Thread comenzando-1 Creando Thread-2 Thread comenzando-2 Thread en ejecución-1 Thread: Thread-1, 4 Thread en ejecución-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
La tabla a continuación enumera algunos métodos importantes de la clase Thread:
Número | Descripción del método |
---|---|
1 | public void start() Haga que la hebra comience a ejecutarse;Java La máquina virtual llama al método run de esta hebra. |
2 | public void run() Si la hebra se construyó usando un objeto Runnable independiente, llama al método run de ese objeto Runnable; de lo contrario, este método no realiza ninguna operación y devuelve. |
3 | public final void setName(String name) Cambia el nombre de la hebra para que sea el mismo que el parámetro name. |
4 | public final void setPriority(int priority) Cambie la prioridad de la hebra. |
5 | public final void setDaemon(boolean on) Marque esta hebra como hebra de守护 o de usuario. |
6 | public final void join(long millisec) Espera que el hilo termine durante el tiempo máximo especificado en millis milisegundos. |
7 | public void interrupt() Interrumpe el hilo. |
8 | public final boolean isAlive() Comprueba si el hilo está en estado activo. |
Comprueba si el hilo está en estado activo. Los siguientes métodos son llamados por el objeto Thread. Los siguientes métodos son métodos estáticos de la clase Thread.
Número | Descripción del método |
---|---|
1 | public static void yield() Pausa el objeto de hilo que se está ejecutando y permite que otros hilos se ejecuten. |
2 | public static void sleep(long millisec) Hace que el hilo actual duerma (se detenga la ejecución) durante el número especificado en milisegundos, esta operación está influenciada por la precisión y la exactitud del reloj del sistema y del planificador. |
3 | public static boolean holdsLock(Object x) Devuelve true solo si el hilo actual mantiene el bloqueo en el objeto especificado. |
4 | public static Thread currentThread() Devuelve una referencia al objeto de hilo que se está ejecutando actualmente. |
5 | public static void dumpStack() Imprime la traza de pila de la hilos actual en el flujo de error estándar. |
El siguiente programa ThreadClassDemo muestra algunos métodos de la clase Thread:
// Nombre de archivo: DisplayMessage.java // Se crea un hilo mediante la implementación de la interfaz Runnable public class DisplayMessage implements Runnable { private String mensaje; public DisplayMessage(String mensaje) { this.mensaje = mensaje; } public void run() { while(true) { System.out.println(mensaje); } } }
// Nombre de archivo: GuessANumber.java // Se crea un hilo mediante la herencia de la clase Thread public class AdivinaUnNúmero extiende Thread { private int número; public GuessANumber(int number) { this.number = number; } public void run() { int contador = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " guesses " + guess); contador++; }; while(guess != number); System.out.println("** ¡Correcto!" + this.getName() + "en" + contador + "guesses.**"); } }
// Nombre de archivo: ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hola"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Iniciando hilo de bienvenida..."); thread1.start(); Runnable bye = new DisplayMessage("Adiós"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Iniciando hilo de despedida..."); thread2.start(); System.out.println("Iniciando hilo...");3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); } System.out.println("Hilo interrumpido."); } System.out.println("Iniciando hilo...");4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() está terminando..."); } }
Los resultados de ejecución son los siguientes, y los resultados pueden variar cada vez que se ejecuta.
Comenzando hilo de hola... Comenzando hilo de despedida... Hola Hola Hola Hola Hola Hola Adiós Adiós Adiós Adiós Adiós .......
1. Crear una clase que implements el interfaz Callable, y realiza la implementación del método call(), que actuará como cuerpo del hilo y tendrá un valor de retorno.
2. Crear un ejemplo de clase que implementa Callable, utilizando FutureTask para encapsular el objeto Callable, el objeto FutureTask encapsula el valor de retorno del método call() de este Callable.
3. Usar el objeto FutureTask como el objetivo del objeto Thread para crear y lanzar un nuevo hilo.
4. Llamar al método get() del objeto FutureTask para obtener el valor de retorno después de que el subproceso finalice.
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0; i < 100;i++) { System.out.println(Thread.currentThread().getName())+" " de la variable de ciclo i+i); if(i==20) { new Thread(ft, "Subproceso con retorno de valor").start(); } } try { System.out.println("El valor de la variable de ciclo i del subproceso:")+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName())+" "+i); } return i; } }
1. Al crear hilos utilizando la implementación de la interfaz Runnable o Callable, la clase del hilo solo implementa la interfaz Runnable o Callable, y puede heredar otras clases.
2. Al crear hilos utilizando la herencia de la clase Thread, es simple de escribir, y si necesita acceder al hilo actual, no necesita usar el método Thread.currentThread(), simplemente use this para obtener el hilo actual.
Al programar en multihilo, debe entender varios conceptos:
Sincronización de hilos
Comunicación entre hilos
Bloqueo de hilos
Control de hilos: suspender, detener y recuperar
La clave para utilizar adecuadamente los hilos es entender que el programa se ejecuta de manera concurrente, no serialmente. Por ejemplo: si hay dos sub sistemas que necesitan ejecutarse de manera concurrente, en este caso se necesita utilizar programación multihilo.
El uso de hilos permite escribir programas muy eficientes. Sin embargo, tenga en cuenta que si crea demasiados hilos, la eficiencia de ejecución del programa realmente disminuye, en lugar de aumentar.
Recuerde que también es importante el costo de cambio de contexto, ¡si crea demasiados hilos, el tiempo que el CPU gasta en cambiar de contexto será mayor que el tiempo que tarda en ejecutar el programa!