1. Procesos e hilos
1. ¿Cuál es el proceso?
Definición estrecha: un proceso es una instancia de un programa de computadora que se está ejecutando.
Definición general: un proceso es una actividad en ejecución de un programa con ciertas funciones independientes con respecto a un determinado conjunto de datos. Es la unidad básica de ejecución dinámica del sistema operativo. En los sistemas operativos tradicionales, los procesos son unidades de asignación básicas y unidades de ejecución básicas.
2. ¿Qué es un hilo?
Los hilos, a veces llamados procesos livianos (LWP), son las unidades más pequeñas del flujo de ejecución del programa. Un subproceso estándar consiste en una ID de subprocesamiento, puntero de instrucción actual (PC), un conjunto de registros y una pila. Además, un hilo es una entidad en el proceso y es la unidad básica que está programada y enviada independientemente por el sistema. El hilo en sí no posee los recursos del sistema, pero solo tiene unos pocos recursos esenciales durante la operación, pero puede compartir todos los recursos propiedad del proceso con otros hilos que pertenecen al mismo proceso.
3. ¿Cuál es la diferencia entre un proceso y un hilo?
La principal diferencia entre procesos y hilos es que son métodos de gestión de recursos de sistemas operativos diferentes.
Un proceso tiene un espacio de direcciones independiente. Después de que un proceso se bloquee, no afectará otros procesos en modo protegido, y un hilo es solo una ruta de ejecución diferente en un proceso.
Los hilos tienen su propia pila y variables locales, pero no hay espacio de dirección separado entre los hilos. Si un hilo muere, significa que todo el proceso muere. Por lo tanto, los programas de procesos múltiples son más robustos que los programas de múltiples hilos, pero al cambiar de procesos, consumen más recursos y son menos eficientes. Sin embargo, para algunas operaciones concurrentes que requieren operaciones concurrentes que requieren compartir ciertas variables, solo pueden usar hilos, no procesos.
En resumen, la diferencia entre un hilo y un proceso es:
(1) Un programa tiene al menos un proceso, y un proceso tiene al menos un hilo;
(2) La escala de división de los hilos es más pequeña que la del proceso, lo que hace que la concurrencia de programas multiproceso sea alta.
(3) El proceso tiene unidades de memoria independientes durante la ejecución, y múltiples hilos comparten memoria, lo que mejora enormemente la eficiencia de operación del programa.
(4) Hay una diferencia entre hilos y procesos durante la ejecución. Cada hilo independiente tiene una entrada para la ejecución del programa, una secuencia de ejecución y una salida para el programa. Sin embargo, los subprocesos no se pueden ejecutar de forma independiente, y deben existir en la aplicación, y la aplicación proporciona múltiples controles de ejecución de hilos.
(5) Desde un punto de vista lógico, el significado de múltiples subprocesos radica en que en una aplicación, múltiples partes de ejecución se pueden ejecutar al mismo tiempo. Sin embargo, el sistema operativo no considera múltiples hilos como múltiples aplicaciones independientes para realizar la programación de procesos, la gestión y la asignación de recursos.
Esta es la diferencia importante entre un proceso y un hilo.
2. El ciclo de vida de un hilo y los cinco estados básicos
Los hilos de Java tienen cinco estados básicos:
(1) Nuevo estado (nuevo): cuando se crea el par de objetos de hilo, ingresa al nuevo estado, como: hilo t = nuevo mythread ();
(2) Estado listo (Runnable): Cuando el método Start () del objeto de subproceso (t.start ();), el hilo ingresa al estado listo. Un hilo en el estado listo solo significa que el hilo está listo y está esperando que la CPU programen la ejecución en cualquier momento, no que el hilo se ejecute inmediatamente después de que se ejecute t.Start ();
(3) Estado en ejecución: cuando la CPU comienza a programar hilos en el estado listo, el hilo se puede ejecutar verdaderamente, es decir, ingresa al estado en ejecución. Nota: El estado listo es la única entrada al estado en ejecución, es decir, si un hilo quiere ingresar al estado en ejecución para ejecutar, primero debe estar en el estado listo;
(4) Estado bloqueado: por alguna razón, un hilo en el estado en funcionamiento da temporalmente el uso de la CPU y detiene la ejecución. En este momento, ingresa al estado de bloqueo. No tendrá la oportunidad de ser llamado nuevamente por la CPU para ingresar al estado en ejecución. Según las razones para el bloqueo, los estados de bloqueo se pueden dividir en tres tipos:
① Esperando el bloqueo: el hilo en el estado en ejecución ejecuta el método Wait () para hacer que el hilo ingrese al estado de bloqueo;
② Bloqueo sincronizado: el hilo no puede adquirir el bloqueo de sincronización sincronizado (porque el bloqueo está ocupado por otros hilos) e ingresará al estado de bloqueo sincronizado;
Bloqueo Otro Bloqueo: Al llamar al sueño del hilo () o unirse () o enviar una solicitud de E/S, el hilo ingresará a un estado de bloqueo. Cuando el estado de sueño () se agotó, unirse () esperó a que el hilo termine o se agotara, o se completó el procesamiento de E/S, el hilo volvió a ingresar al estado listo.
(5) Estado muerto: el hilo ha terminado de ejecutar o salir del método run () debido a una excepción, y el hilo finaliza su ciclo de vida.
3. Implementación de Java Multithreading
En Java, si desea implementar un programa de múltiples subprocesos, debe confiar en la clase principal de un hilo (como el concepto de una clase principal, que representa la clase principal de un hilo), pero la clase principal de este hilo debe tener algunos requisitos especiales al definirlo. Esta clase puede heredar la clase de subprocesos o implementar la interfaz ejecutable para completar la definición.
1. Heredar la clase de subprocesos para implementar múltiples subprocesos
java.lang.thread es una clase responsable de las operaciones de subprocesos. Cualquier clase puede convertirse en la clase principal de un hilo si hereda la clase de hilo. Dado que es la clase principal, debe tener sus métodos de uso, y el método principal iniciado por el hilo debe sobrescribir el método run () en la clase de subprocesos.
Defina la clase del cuerpo de un hilo:
La clase MyThread extiende el hilo {// La clase principal del título de cadena privada de hilo; public mythread (título de cadena) {this.title = title; } @Override public void run () {// El método principal del hilo para (int x = 0; x <10; x ++) {system.out.println (this.title + "run, x =" + x); }}}Ahora que hay una clase de subprocesos y hay métodos de operación correspondientes, se debe generar el objeto y se deben llamar a los métodos interiores, por lo que se escribe el siguiente programa:
public class testDemo {public static void main (string [] args) {myThread mt1 = new MyThread ("Thread A"); MyThread mt2 = new MyThread ("Thread B"); MyThread mt3 = new MyThread ("Thread C"); mt1.run (); mt2.run (); mt3.run (); }Resultados de ejecución:
Hilo A Ejecuta, x = 0
Hilo A Ejecuta, x = 1
Hush A corre, x = 2
Hilo A Ejecuta, x = 3
Hush A corre, x = 4
Hush A corre, x = 5
Hush A corre, x = 6
Hush A corre, x = 7
Hustar A corre, x = 8
Enhebrar A corre, x = 9
El hilo B se ejecuta, x = 0
El hilo B funciona, x = 1
El hilo B funciona, x = 2
El hilo B funciona, x = 3
El hilo B funciona, x = 4
El hilo B funciona, x = 5
El hilo B funciona, x = 6
El hilo B funciona, x = 7
El hilo B funciona, x = 8
El hilo B funciona, x = 9
El hilo C se ejecuta, x = 0
El hilo C se ejecuta, x = 1
El hilo C se ejecuta, x = 2
El hilo C se ejecuta, x = 3
El hilo C funciona, x = 4
El hilo C funciona, x = 5
El hilo C funciona, x = 6
El hilo C funciona, x = 7
El hilo C funciona, x = 8
El hilo C funciona, x = 9
Descubrimos que las operaciones anteriores no inician realmente múltiples subprocesos, porque la ejecución de múltiples subprocesos debe ejecutarse alternativamente, y en este momento se ejecuta secuencialmente, y el código de cada objeto continuará ejecutándose hacia abajo después de ejecutar el código de cada objeto.
Si realmente desea comenzar a múltiples subprocesos en un programa, debe confiar en un método de la clase de subproceso: public void start (), lo que significa que realmente comienza a múltiples subprocesos. Después de llamar a este método, el método run () se llamará indirectamente:
public class testDemo {public static void main (string [] args) {myThread mt1 = new MyThread ("Thread A"); MyThread mt2 = new MyThread ("Thread B"); MyThread mt3 = new MyThread ("Thread C"); mt1.start (); mt2.start (); mt3.start (); }}Resultados de ejecución:
El hilo C se ejecuta, x = 0
Hilo A Ejecuta, x = 0
El hilo B se ejecuta, x = 0
Hilo A Ejecuta, x = 1
El hilo C se ejecuta, x = 1
Hush A corre, x = 2
El hilo B funciona, x = 1
Hilo A Ejecuta, x = 3
Hush A corre, x = 4
Hush A corre, x = 5
El hilo C se ejecuta, x = 2
El hilo C se ejecuta, x = 3
El hilo C funciona, x = 4
El hilo C funciona, x = 5
El hilo C funciona, x = 6
El hilo C funciona, x = 7
El hilo C funciona, x = 8
El hilo C funciona, x = 9
Hush A corre, x = 6
Hush A corre, x = 7
Hustar A corre, x = 8
Enhebrar A corre, x = 9
El hilo B funciona, x = 2
El hilo B funciona, x = 3
El hilo B funciona, x = 4
El hilo B funciona, x = 5
El hilo B funciona, x = 6
El hilo B funciona, x = 7
El hilo B funciona, x = 8
El hilo B funciona, x = 9
En este momento, puede encontrar que múltiples hilos se ejecutan alternativamente entre sí, pero los resultados de cada ejecución son diferentes. A través del código anterior, podemos sacar una conclusión: si desea iniciar un hilo, debe confiar en el método Start () de la clase de subprocesos para ejecutar. Después de que se inicia el hilo, el método run () se llamará de forma predeterminada.
Después de llamar al método Start (), sucedieron una serie de cosas complicadas:
(1) iniciar un nuevo hilo de ejecución (con una nueva pila de llamadas);
(2) el hilo se transfiere del nuevo estado al estado ejecutable;
(3) Cuando el hilo tiene la oportunidad de ejecutar, su método de ejecución de destino () se ejecutará.
Nota: Para Java, el método Run () no tiene nada especial. Al igual que el método Main (), solo significa que el nuevo hilo conoce el nombre del método (y la firma) de la llamada. Por lo tanto, es legal llamar al método Ejecutar en Runnable o Thread, pero no inicia un nuevo hilo.
Explicación: ¿Por qué tienes que llamar a Start () en lugar de llamar directamente a Run () cuando comienza un hilo?
Descubrimos que después de llamar a Start (), en realidad ejecuta el método run () anulación de ejecución (), entonces, ¿por qué no llamar al método run () directamente? Para explicar este problema, abra el código fuente de la clase de subprocesos y observe la definición del método Start ():
public sincronized void start () {if (threadstatus! = 0) tirar nueva ilegalThreadStateException (); Group.add (esto); boolean comenzó = falso; intente {start0 (); iniciado = verdadero; } Finalmente {try {if (! iniciado) {group.threadstartFailed (this); }} catch (lateBable ignore) {}}} private nativo void start0 ();Abra el código fuente de este método y primero encontrará que el método lanzará una excepción de "IllegalThreadStateException". En términos generales, si un método usa lanzamiento para lanzar un objeto de excepción, entonces esta excepción debe ser captada usando intento ... captura o lanzado usando lanzamientos en la declaración del método, pero no hay nada en esta área. ¿Por qué? Porque esta clase de excepción pertenece a una subclase de excepción de tiempo de ejecución (RuntimeException):
java.lang.object
|- java.lang.throwable
|- java.lang.Exception
|- java.lang.runtimeException
|- java.lang.iLLEGALARGUNMEXCEPTION
|- java.lang.illegalthreadstateException
Esta excepción se lanzará cuando se inicie un objeto de hilo repetidamente, es decir, un objeto de hilo solo se puede iniciar una vez.
Una de las partes más críticas del método Start () es el método Start0 (), y este método utiliza una definición de palabra clave nativa.
La palabra clave nativa se refiere a la interfaz nativa de Java, es decir, Java se usa para llamar a las funciones de función del sistema operativo nativo para completar algunas operaciones especiales. Dicho desarrollo del código rara vez se ve en Java porque la característica más importante de Java es la portabilidad. Si un programa solo se puede usar en un sistema operativo fijo, la portabilidad se perderá por completo, por lo que esta operación generalmente no se usa.
La implementación de múltiples lecturas debe requerir el soporte del sistema operativo. Entonces el método Start0 () anterior es en realidad muy similar al método abstracto sin un cuerpo del método. Este cuerpo del método se entrega al JVM para implementar, es decir, el JVM en Windows puede usar el método A A para implementar Start0 (), mientras que el JVM en Linux puede usar el método B para implementar Start0 (), pero al llamar, no se preocupará por el método específico de implementar el método Start0 (), pero solo se preocupa por el resultado de la operación final, y entregada a JVM para que coincida con los sistemas operativos diferentes.
Por lo tanto, en las operaciones de subprocesos múltiples, el uso del método Start () para iniciar operaciones multiproceso requiere llamadas a la función del sistema operativo.
2. Implemente la interfaz ejecutable para implementar múltiples subprocesos
El uso de la clase de subprocesos puede facilitar la implementación de múltiples subprocesos, pero la mayor desventaja de este método es el problema de la herencia única. Para este fin, la interfaz ejecutable también se puede usar en Java para implementar múltiples subprocesos. La definición de esta interfaz es la siguiente:
Public Interface Runnable {public void run ();}Implementar múltiples subprocesos a través de la interfaz ejecutable:
Clase MyThread implementos Runnable {// El título de cadena privada de clase principal del hilo; public mythread (título de cadena) {this.title = title; } @Override public void run () {// El método principal del hilo para (int x = 0; x <10; x ++) {system.out.println (this.title + "run, x =" + x); }}}Esto no es muy diferente de la forma anterior de heredar las clases de hilos, pero una ventaja es que evita la limitación de la herencia única.
Pero el problema está aquí. Como se mencionó anteriormente, si desea iniciar múltiples subprocesos, debe confiar en el método Start () de la clase de subprocesos. Cuando heredas la clase de subprocesos, puedes heredar directamente este método y usarlo. Pero ahora está implementando la interfaz runable. Sin este método, puede heredarlo. ¿Qué debes hacer?
Para resolver este problema, aún debe confiar en la clase de hilo para completarlo. Un constructor se define en la clase de subprocesos para recibir el objeto de interfaz ejecutable:
hilo público (objetivo runnable);
Inicie multithreading usando la clase de subprocesos:
public class testDemo {public static void main (string [] args) lanza la excepción {myThread mt1 = new MyThread ("Thread A"); MyThread mt2 = new MyThread ("Thread B"); MyThread mt3 = new MyThread ("Thread C"); nuevo hilo (mt1) .Start (); nuevo hilo (mt2) .Start (); nuevo hilo (mt3) .Start (); }}Resultados de ejecución:
Hilo A Ejecuta, x = 0
El hilo B se ejecuta, x = 0
El hilo B funciona, x = 1
El hilo C se ejecuta, x = 0
El hilo B funciona, x = 2
Hilo A Ejecuta, x = 1
El hilo B funciona, x = 3
El hilo C se ejecuta, x = 1
El hilo C se ejecuta, x = 2
El hilo B funciona, x = 4
El hilo B funciona, x = 5
Hush A corre, x = 2
Hilo A Ejecuta, x = 3
Hush A corre, x = 4
Hush A corre, x = 5
Hush A corre, x = 6
Hush A corre, x = 7
Hustar A corre, x = 8
Enhebrar A corre, x = 9
El hilo B funciona, x = 6
El hilo B funciona, x = 7
El hilo B funciona, x = 8
El hilo B funciona, x = 9
El hilo C se ejecuta, x = 3
El hilo C funciona, x = 4
El hilo C funciona, x = 5
El hilo C funciona, x = 6
El hilo C funciona, x = 7
El hilo C funciona, x = 8
El hilo C funciona, x = 9
En este momento, no solo se logra una startup múltiple, sino que también no hay limitaciones de herencia única.
3. El tercer método para implementar múltiples subprocesos: use la interfaz de llamada para implementar múltiples subprocesos
La lectura múltiple utilizando la interfaz ejecutable puede evitar la limitación de la herencia única, pero hay un problema de que el método run () en la interfaz ejecutable no puede devolver el resultado de la operación. Para resolver este problema, se proporciona una nueva interfaz: la interfaz invocable (java.util.concurrent.callable).
Interfaz pública Callable <V> {public v Call () arroja excepción;}Después de ejecutar el método de llamada () en la interfaz de llamada, se devolverá un resultado. El tipo de resultado devuelto está determinado por los genéricos en la interfaz invocable.
La operación específica de implementar la interfaz invocable para implementar múltiples subprocesos es:
Cree una clase de implementación de la interfaz llamable e implementa el método clall (); Luego use la clase FUTURETASK para envolver el objeto de la clase de implementación invocable y use este objeto FutureTask como el objetivo del objeto de subproceso para crear un hilo.
Defina una clase de cuerpo de hilo:
import java.util.concurrent.callable; class myThread implementa llamar <string> {private int ticket = 10; @Override public String Call () lanza la excepción {for (int i = 0; i <20; i ++) {if (this.ticket> 0) {system.out.println ("Vender boletos, el número restante de votos es"+this.Ticket -); }} return "Se han agotado los boletos"; }}La clase Thread no admite directamente la interfaz de llamada. Después de JDK1.5, se proporciona una clase:
java.util.concurrent.futureTask <v>
Esta clase es el principal responsable del funcionamiento del objeto de interfaz invocable. Su estructura de definición es la siguiente:
clase pública Futuretask <v>
extiende objeto
implementa runnableFurTure <v>
La interfaz RunnableFurture tiene la siguiente definición:
interfaz pública runnableFurture <v>
extiende Runnable, Future <V>
El siguiente constructor se define en la clase FutureTask:
Public FutUreTask (llamable <v> llamable)
Ahora, finalmente puede recibir el objeto de interfaz de llamada a través de la clase FutureTask. El propósito de recibir es obtener el resultado de retorno del método de llamada ().
Del análisis anterior podemos encontrar:
La clase FutureTask puede recibir objetos de interfaz llamables, y la clase FutureTask implementa la interfaz RunnableFurTure, que hereda la interfaz ejecutable.
Entonces, podemos comenzar a múltiples lecturas así:
Public Class TestDemo {public static void main (string [] args) lanza excepción {myThread mt1 = new MyThread (); MyThread mt2 = new MyThread (); FUTURETASK <String> tarea1 = new FuturetAk <String> (mt1); // get call () método para devolver el resultado FutureTask <String> task2 = new FutureTask <String> (MT2); // El método Get Call () para devolver el resultado // FutureTask es una subclass de la interfaz ejecutable. Puede usar la construcción de la clase de hilo para recibir objetos de tarea New Thread (tarea1) .Start (); nuevo hilo (tarea2) .Start (); // Después de completar la ejecución multithread, puede usar el método get () en el futuro de la interfaz principal de FutureTask para obtener el resultado de la ejecución System.out.println ("Resultado de retorno de hilo 1:"+tarea1.get ()); System.out.println ("Resultado de retorno de hilo 2:"+task2.get ()); }}Resultados de ejecución:
Vender boletos, el número restante de votos es 10
Vender boletos, el número restante de votos es 10
Vender boletos, el número restante de votos es 9
Vender boletos, el número restante de votos es 8
Vender boletos, el número restante de votos es 7
Vender boletos, el número restante de votos es 9
Vender boletos, el número restante de votos es 6
Vender boletos, el número restante de votos es 8
Vender boletos, el número restante de votos es 5
Vender boletos, el número restante de votos es 7
Vender boletos, el número restante de votos es 4
Vender boletos, el número restante de votos es 6
Vender boletos, el número restante de votos es 3
Vender boletos, el número restante de votos es 5
Vender boletos, el número restante de votos es 2
Vender boletos, el número restante de votos es 4
Vender boletos, el número restante de votos es 1
Vender boletos, el número restante de votos es 3
Vender boletos, el número restante de votos es 2
Vender boletos, el número restante de votos es 1
El resultado de devolución del hilo 1: el boleto se ha agotado. El resultado de devolución del hilo 2: el boleto se ha agotado.
resumen:
Lo anterior explica tres formas de implementar múltiples lectura. Para el inicio de subprocesos, todos se llaman el método Start () del objeto de subproceso. Es importante tener en cuenta que el método Start () no se puede llamar dos veces en el mismo objeto de subproceso.