Tabla de contenido(?)[-]
Uno extiende la clase dos de Javalangthread implementa la interfaz javalangrunnable tres diferencias entre el hilo y la transición de estado de cuatro hilos de hilo de cinco hilos programando seis funciones comunes que explican cómo usar por qué se usa el método de unión siete explicación de hilo común explicación ocho sincronización de hilos nine hilt data transmisión de datos de hilos
Este artículo habla principalmente sobre los métodos de uso de múltiples subprocesos en Java, sincronización de subprocesos, transferencia de datos de subprocesos, estado de hilo y uso de funciones de hilo correspondientes y descripción general.
Primero, hablemos de la diferencia entre un proceso y un hilo:
Proceso: cada proceso tiene código independiente y espacio de datos (contexto del proceso), y el cambio entre procesos tendrá una sobrecarga grande. Un proceso contiene hilos 1-N.
Tema: El mismo tipo de hilos comparten código y espacio de datos. Cada hilo tiene una pila de ejecución independiente y contador de programa (PC), y la sobrecarga de cambio de hilo es pequeña.
Al igual que un proceso, los hilos se dividen en cinco etapas: creación, lista, ejecutándose, bloqueando y terminando.
Multiprocess significa que el sistema operativo puede ejecutar múltiples tareas (programas) al mismo tiempo.
Multithreading se refiere a múltiples secuencias secuenciales que se ejecutan en el mismo programa.
En Java, hay dos formas de implementar múltiples subprocesos. Una es continuar con la clase de subprocesos, y la otra es implementar la interfaz runable.
1. Extienda la clase java.lang.thread
paquete com.multithread.learning;/***@Funccton Multithreading Learning*@autor Lin Bingwen*@Time 2015.3.9*/class Thread1 extiende Thread {Nombre de cadena privada; public Thread1 (nombre de cadena) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {system.out.println (nombre + "run:" + i); intente {dormir ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} public class Main {public static void main (string [] args) {Thread1 mth1 = new Thread1 ("A"); Thread1 mth2 = new Thread1 ("B"); mth1.start (); mth2.start (); }} Producción:
Una carrera: 0
B Run: 0
Una carrera: 1
Una carrera: 2
Una carrera: 3
Una carrera: 4
B Run: 1
B Run: 2
B Run: 3
B Run: 4
Ejecutarlo de nuevo:
Una carrera: 0
B Run: 0
B Run: 1
B Run: 2
B Run: 3
B Run: 4
Una carrera: 1
Una carrera: 2
Una carrera: 3
Una carrera: 4
ilustrar:
Cuando el programa comienza y se ejecuta principalmente, la máquina virtual Java inicia un proceso, y el hilo principal se crea cuando se llama Main (). Con el método de inicio de los dos objetos de Mitisay, también se inician los otros dos hilos, de modo que toda la aplicación se ejecuta bajo múltiples hilos.
Nota: El método Start () se llama a no ejecutar código multiproceso de inmediato, sino que hace que el hilo se convierta en un estado ejecutable. Cuando se ejecuta está determinado por el sistema operativo.
A partir de los resultados del programa en ejecución, podemos encontrar que los programas de múltiples subprocesos se ejecutan fuera de orden. Por lo tanto, solo el código ejecutado fuera de pedido debe diseñarse como multiproceso.
El propósito de la invocación del método Thread.sleep () es evitar que el hilo actual ocupe los recursos de la CPU obtenidos solo por el proceso, para dejar una cierta cantidad de tiempo para que otros hilos se ejecuten.
De hecho, la orden de ejecución de todo el código multiproceso es incierto, y los resultados de cada ejecución son aleatorios.
Sin embargo, si el método de inicio se llama repetidamente, se producirá un java.lang.illegalthreadstateException.
Thread1 mth1 = new Thread1 ("A"); Thread1 mth2 = mth1; mth1.start (); mth2.start (); Producción:
Excepción en el hilo "principal" java.lang.illegalthreadstateException
en java.lang.thread.start (fuente desconocida)
en com.multithread.learning.main.main (main.java:31)
Una carrera: 0
Una carrera: 1
Una carrera: 2
Una carrera: 3
Una carrera: 4
2. Implemente la interfaz java.lang.runnable
/***@Functon Multithreading Learning*@autor Lin Bingwen*@Time 2015.3.9*/paquete com.multithread.runnable; class Thread2 implementa runnable {name de cadena privada; public Thread2 (nombre de cadena) {this.name = name; } @Override public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run:" + i); intente {Thread.sleep ((int) Math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} public class Main {public static void main (string [] args) {new Thread (new Thread2 ("C")). Start (); nuevo hilo (nuevo Thread2 ("D")). Start (); }} Producción:
C Run: 0
D Run: 0
D Run: 1
C Run: 1
D Run: 2
C Run: 2
D Run: 3
C Run: 3
D Run: 4
C Run: 4
ilustrar:
La clase Thread2 implementa la interfaz Runnable, que hace que la clase tenga las características de una clase multiproceso. El método Run () es una convención para programas multiproceso. Todo el código multiproceso está en el método Ejecutar. La clase de hilo es en realidad una clase que implementa la interfaz ejecutable.
Al iniciar múltiples subprocesos, primero debe construir el objeto a través del subproceso de constructor de la clase de subproceso (destino runnable), y luego llamar al método Start () del objeto de subproceso para ejecutar el código de múltiples subprocesos.
De hecho, todo el código multiproceso se ejecuta ejecutando el método Start () de Thread. Por lo tanto, ya sea extender la clase de subprocesos o implementar la interfaz ejecutable para implementar múltiples subprocesos o, en última instancia, controlar los subprocesos a través de la API del objeto de subprocesos, familiarizarse con la API de la clase de hilo es la base de la programación de múltiples hilos.
3. La diferencia entre hilo y runnable
Si una clase hereda el hilo, no es adecuado para compartir recursos. Sin embargo, si se implementa la interfaz runable, es fácil implementar el intercambio de recursos.
paquete com.multithread.learning;/***@Functon Multi-Thread Learning, Hereding Thread, recursos no se pueden compartir*@autor Lin Bingwen*@Time 2015.3.9*/class Thread1 extiende Thread {private int count = 5; nombre de cadena privada; public Thread1 (nombre de cadena) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {system.out.println (name + "run count =" + count--); intente {dormir ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} public class Main {public static void main (string [] args) {Thread1 mth1 = new Thread1 ("A"); Thread1 mth2 = new Thread1 ("B"); mth1.start (); mth2.start (); }} Producción:
B Run Count = 5
Un recuento de ejecuciones = 5
B Run Count = 4
B Run Count = 3
B Run Count = 2
B Run Count = 1
Un recuento de ejecuciones = 4
Un recuento de ejecuciones = 3
Un recuento de ejecuciones = 2
Un recuento de ejecuciones = 1
De lo anterior, podemos ver que los recuentos son diferentes entre diferentes hilos, lo que tendrá un gran problema para el sistema de venta de boletos. Por supuesto, la sincronización se puede usar aquí. Usemos Runnable para hacerlo aquí
/***@Funccton Multi-Threading Learning Hereds Runnable, los recursos se pueden compartir*@autor Lin Bingwen*@Time 2015.3.9*/paquete com.multithread.runnable; class Thread2 implementos runnables {private int count = 15; @Override public void run () {for (int i = 0; i <5; i ++) {system.out.println (thread.currentThread (). GetName () + "run count =" + count--); intente {Thread.sleep ((int) Math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }}}}} public class Main {public static void main (string [] args) {thread2 my = new Thread2 (); nuevo hilo (my, "c"). start (); // el mismo mt, pero no es posible en el hilo. Si instancia el objeto MT, una excepción aparecerá nueva hilo (mi, "d"). Inicio (); nuevo hilo (my, "e"). start (); }} Producción:
C Run Count = 15
D Run Count = 14
E RUN COUNT = 13
D Run Count = 12
D Run Count = 10
D Run Count = 9
D Run Count = 8
C Run Count = 11
E RUN READ = 12
C Run Count = 7
E RUN COUNT = 6
C Run Count = 5
E Run ROPLE = 4
C Run Count = 3
E Run ROPLE = 2
Aquí debemos tener en cuenta que cada hilo usa el mismo objeto de instanciación. Si no es el mismo, ¡el efecto será el mismo que el anterior!
Resumir:
Las ventajas de implementar la interfaz ejecutable sobre heredar la clase de subprocesos:
1): Adecuado para múltiples hilos con el mismo código de programa para procesar el mismo recurso
2): puede evitar la limitación de la herencia única en Java
3): Aumente la robustez del programa, el código se puede compartir mediante múltiples subprocesos y el código y los datos son independientes
Déjame recordarte: el método principal es en realidad un hilo. En Java, los hilos se inician al mismo tiempo. En cuanto a cuándo y cuál se ejecuta primero, depende completamente de quién obtiene los recursos de la CPU primero.
En Java, se inician al menos 2 hilos cada vez que se ejecuta el programa. Uno es el hilo principal y el otro es el hilo de recolección de basura. Porque cada vez que se ejecute una clase usando comandos Java, se iniciará un JVM, y cada pasantía de JVM inicia un proceso en el sistema operativo.
4. Transición de estado de hilo
1. Nuevo estado (nuevo): se crea un nuevo objeto de hilo.
2. Estado listo (Runnable): después de que se crea el objeto de subproceso, otros subprocesos llaman al método Start () del objeto. El hilo en este estado se encuentra en el grupo de hilos ejecutables y se vuelve ejecutable, esperando obtener los derechos de uso de la CPU.
3. Estado en ejecución: el hilo en el estado listo adquiere la CPU y ejecuta el código del programa.
4. Estado bloqueado: Estado bloqueado significa que el hilo renuncia a los derechos de uso de la CPU por alguna razón y deja de funcionar temporalmente. No es hasta que el hilo ingresa al estado listo que tiene la oportunidad de ir al estado en funcionamiento. Hay tres tipos de bloqueo:
(1) Esperando bloquear: El hilo en ejecución ejecuta el método Wait (), y el JVM colocará el hilo en la piscina de espera.
(2) Bloqueo sincrónico: cuando el hilo en ejecución adquiere el bloqueo de sincronización del objeto, si el bloqueo de sincronización está ocupado por otros hilos, el JVM colocará el hilo en la piscina de bloqueo.
(Iii), otro bloqueo: cuando un hilo en ejecución ejecuta el método sleep () o unir (), o emite una solicitud de E/S, el JVM establecerá el hilo en 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.
5. Programación de hilos
Programación de hilos
1. Ajuste la prioridad del hilo: los hilos de Java tienen prioridad, y los hilos con alta prioridad tendrán más oportunidades para funcionar.
La prioridad de los hilos Java está representada por enteros, con un rango de valor de 1 ~ 10. La clase de hilo tiene las siguientes tres constantes estáticas:
static int max_priority
La prioridad más alta que puede tener un hilo es 10.
static int min_priority
La prioridad más baja que puede tener un hilo es 1.
static int norm_priority
La prioridad predeterminada asignada al hilo es 5.
Los métodos setPriority () y getPriority () de la clase de subprocesos se utilizan para establecer y obtener la prioridad del hilo respectivamente.
Cada hilo tiene una prioridad predeterminada. La prioridad predeterminada del hilo principal es thread.norm_priority.
La prioridad de los hilos se hereda. Por ejemplo, si el hilo B se crea en el hilo A, entonces B tendrá la misma prioridad que A.
El JVM proporciona 10 prioridades de subprocesos, pero no se asigna bien con los sistemas operativos comunes. Si desea que el programa se porte a cada sistema operativo, solo debe usar la clase de subprocesos con las siguientes tres constantes estáticas como prioridad, lo que puede garantizar que la misma prioridad adopte el mismo método de programación.
2. Duerme del hilo: hilo. Método de sueño (Long Millis) Para que el hilo vaya a un estado de bloqueo. El parámetro Millis establece el tiempo de sueño en milisegundos. Cuando termina el sueño, se vuelve ejecutable. La plataforma Sleep () tiene una buena portabilidad.
3. Hilo Wait: el método Wait () en la clase de objeto hace que el hilo actual espere hasta que otros subprocesos llamen al método de activación del objeto notify () o notifyAll (). Estos dos métodos de atención también son métodos en la clase de objetos, y su comportamiento es equivalente a llamar a la espera (0).
4. Concesiones de hilos: el método thread.yield () suspende el objeto de subproceso que ejecuta actualmente y brinda la oportunidad de ejecución de los hilos con la misma prioridad o mayor.
5. Hilo unirse: Método unirse (), esperando que termine otros hilos. Llamando al método Join () de otro hilo en el hilo actual, el hilo actual va a un estado de bloqueo hasta que se ejecuta el otro proceso, y el hilo actual va del bloqueo al estado listo.
6. Wail Wake-up: el método notify () en la clase de objeto despierta un solo hilo esperando en este monitor de objeto. Si todos los hilos están esperando en este objeto, se seleccionará uno de los hilos. La elección es arbitraria y ocurre al tomar una decisión sobre la implementación. El hilo espera en el monitor del objeto llamando a uno de los métodos de espera. El hilo Waken no se puede ejecutar hasta que el hilo actual abandone el bloqueo de este objeto. El hilo Waken competirá con todos los demás hilos que se sincronizan activamente en el objeto de manera convencional; Por ejemplo, el hilo Waken no tiene privilegios o desventajas confiables al ser el siguiente hilo que bloquea este objeto. Un método similar también tiene un notifyAll () que despierta todos los hilos que esperan en este monitor de objeto.
NOTA: Los dos métodos suspender () y reanudar () en hilo se han abolido en JDK1.5 y no se introducirán nuevamente. Porque hay una tendencia a un punto muerto.
6. Descripción de funciones comunes
① Sleep (Long Millis): deje que el hilo que ejecute actualmente se ejecute dentro del número especificado de milisegundos (suspender la ejecución)
②Join (): se refiere a esperar que el hilo T termine.
Cómo usarlo.
Join es un método de la clase de hilo. Se llama directamente después de comenzar el hilo. Es decir, la función de unión () es: "Espere a que termine el hilo". Lo que debe entenderse aquí es que el hilo se refiere al hilo principal que espera que el hilo infantil termine. Es decir, el código después del hilo infantil llama al método unión (), y solo se puede ejecutar hasta que el hilo infantil esté terminado.
Thread t = new Athread (); t.Start (); t.Join ();
Por qué usar el método unitar ()
En muchos casos, el hilo principal genera e inicia el hilo infantil. Si se requiere una gran cantidad de operaciones que consumen mucho tiempo en el hilo infantil, el hilo principal a menudo terminará antes del hilo infantil. Sin embargo, si el hilo principal necesita usar el resultado de procesamiento del hilo infantil después del procesamiento de otras transacciones, es decir, el hilo principal debe esperar a que el hilo infantil complete la ejecución antes de finalizar. En este momento, se debe utilizar el método Join ().
No se une. /** *@Functon Multithreading Learning, Únete *@autor Lin Bingwen *@Time 2015.3.9 */paquete com.multithread.join; class Thread1 extiende Thread {Nombre de cadena privada; public Thread1 (nombre de cadena) {super (nombre); this.name = name; } public void run () {System.out.println (thread.currentThread (). getName () + "Thread comienza!"); for (int i = 0; i <5; i ++) {system.out.println ("subcread"+name+"run:"+i); intente {dormir ((int) math.random () * 10); } catch (InterruptedException e) {E.PrintStackTrace (); }} System.out.println (thread.currentThread (). GetName () + "¡los extremos de la ejecución de hilos!"); }} clase pública Main {public static void main (String [] args) {System.out.println (Thread.CurrentThread (). Thread1 mth1 = new Thread1 ("A"); Thread1 mth2 = new Thread1 ("B"); mth1.start (); mth2.start (); System.out.println (thread.currentThread (). GetName ()+ "¡los extremos de ejecución de hilo principal!"); }} Resultado de salida:
El hilo principal principal comienza a funcionar!
¡Los principales hilos principales se ejecutan en funcionamiento!
B Comienza la carrera de hilo!
El hilo del niño B funciona: 0
¡Comienza una carrera de hilo!
Hilo infantil A corre: 0
El hilo del niño B funciona: 1
Hilo infantil A corre: 1
Hilo infantil A corre: 2
Hilo infantil A corre: 3
Hilo infantil A corre: 4
¡Una carrera de hilo termina!
El hilo del niño B funciona: 2
El hilo del niño B funciona: 3
El hilo del niño B funciona: 4
B El hilo se extiende!
Descubrió que el hilo principal terminó antes que el hilo infantil
Unirse
Public Class Main {public static void main (string [] args) {System.out.println (Thread.CurrentThread (). GetName ()+"¡Inicio de ejecución de hilo principal!"); Thread1 mth1 = new Thread1 ("A"); Thread1 mth2 = new Thread1 ("B"); mth1.start (); mth2.start (); intente {mth1.Join (); } catch (InterruptedException e) {E.PrintStackTrace (); } try {mth2.Join (); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println (thread.currentThread (). GetName ()+ "¡Los extremos de ejecución del hilo principal!"); }} Resultados de ejecución:
El hilo principal principal comienza a funcionar!
¡Comienza una carrera de hilo!
Hilo infantil A corre: 0
B Comienza la carrera de hilo!
El hilo del niño B funciona: 0
Hilo infantil A corre: 1
El hilo del niño B funciona: 1
Hilo infantil A corre: 2
El hilo del niño B funciona: 2
Hilo infantil A corre: 3
El hilo del niño B funciona: 3
Hilo infantil A corre: 4
El hilo del niño B funciona: 4
¡Una carrera de hilo termina!
El hilo principal definitivamente esperará hasta que los hilos de los niños estén terminados antes de que termine.
③yield (): Pausa el objeto de subproceso que se ejecuta actualmente y ejecuta otros hilos.
La función del método Thread.yield () es: Pausa el objeto de subproceso que actualmente ejecuta y ejecuta otros subprocesos.
Lo que debería hacer () es hacer que el hilo actual de funcionamiento vuelva al estado runnable para permitir que otros hilos con la misma prioridad obtengan una oportunidad de correr. Por lo tanto, el propósito de usar el rendimiento () es permitir que los hilos de la misma prioridad funcionen adecuadamente. Sin embargo, en realidad, el rendimiento () no se puede garantizar para lograr el propósito de la concesión, porque el hilo del hilo puede ser seleccionado nuevamente por el planificador de hilos.
Conclusión: el rendimiento () Nunca hace que el hilo vaya al estado de espera/sueño/bloqueo. En la mayoría de los casos, el rendimiento () hará que el hilo pase del estado en funcionamiento a la ejecución, pero puede que no funcione. Puedes ver la imagen de arriba.
/** *@Functon Multithreading Learning rendimiento *@autor Lin Bingwen *@Time 2015.3.9 */paquete com.multithread.yield; class Threadyield extiende Thread {public Threadyield (name de cadena) {super (nombre); } @Override public void run () {for (int i = 1; i <= 50; i ++) {system.out.println ("" + this.getName () + "-----" + i); // Cuando tengo 30 años, el hilo renunciará al tiempo de la CPU y dejará que otros o sus propios hilos se ejecuten (es decir, quien lo agarre por primera vez) si (i == 30) {this.yield (); }}}} public class Main {public static void main (string [] args) {Threadyield yt1 = new Threadyield ("Zhang San"); Threadyield yt2 = new Threadyield ("Li Si"); yt1.start (); yt2.start (); }} Resultados de ejecución:
El primer caso: Li Si (Thread) obtendrá el tiempo de la CPU cuando se ejecute a 30. En este momento, Zhang San (hilo) toma el tiempo de la CPU y lo ejecuta.
La segunda situación: cuando Li Si (Thread) se ejecute a 30, el tiempo de la CPU será abandonado. En este momento, Li Si (Thread) toma el tiempo de la CPU y lo ejecuta.
La diferencia entre el sueño () y el rendimiento ()
La diferencia entre Sleep () y el rendimiento ()): Sleep () hace que el hilo actual ingrese a un estado estancado, por lo que el hilo que ejecuta el sueño () definitivamente no se ejecutará dentro del tiempo especificado; El rendimiento () simplemente hace que el hilo actual regrese al estado ejecutable, por lo que el hilo que ejecuta el rendimiento () puede ejecutarse inmediatamente después de ingresar al estado ejecutable.
El método de sueño hace que el hilo actualmente se ejecute durante un período de tiempo y entra en un estado innecesable. La duración de este período es establecida por el programa. El método de rendimiento permite que el hilo actual renuncie a la propiedad de la CPU, pero el tiempo de la transferencia es inestable. De hecho, el método de rendimiento () corresponde a la siguiente operación: primero verifique si hay subprocesos con la misma prioridad actualmente en el mismo estado ejecutable. Si es así, entregue la propiedad de la CPU a este hilo, de lo contrario, continúe ejecutando el hilo original. Entonces, el método de rendimiento () se llama "concesión", que brinda la oportunidad de correr a otros hilos con la misma prioridad
Además, el método de sueño permite que los hilos de menor prioridad obtengan oportunidades de ejecución, pero cuando se ejecuta el método de rendimiento (), el hilo actual todavía está en un estado ejecutable, por lo que es imposible ceder subprocesos de menor prioridad para obtener la propiedad de la CPU más adelante. En un sistema en ejecución, si el subproceso de mayor prioridad no llama al método de sueño y no está bloqueado por E/S, entonces el hilo de prioridad más bajo solo puede esperar a que se ejecute todos los hilos de mayor prioridad para tener la oportunidad de ejecutar.
④SetPriority (): Cambie la prioridad del hilo.
Min_priority = 1
Norm_priority = 5
Max_priority = 10
uso:
Thread4 t1 = new Thread4 ("T1");
Thread4 t2 = new Thread4 ("T2");
t1.setPriority (hilo.max_priority);
t2.setpriority (hilo.min_priority);
⑤interrupt (): interrumpir un hilo. Este método final es bastante duro. Si el hilo T abre un recurso y no ha tenido tiempo de cerrarlo, es decir, el método de ejecución se ve obligado a finalizar el hilo antes de que se haya ejecutado, lo que hará que el recurso no se cierre.
La mejor manera de finalizar el proceso es usar el programa de ejemplo de la función Sleep (). Se usa una variable booleana en la clase de subprocesos para controlar cuando finaliza el método Run (). Una vez que termina el método Run (), el hilo termina.
⑥wait ()
Obj.wait () y obj.notify () debe usarse con sincronizado (obj), es decir, esperar y notificar operar en el bloqueo OBJ que se ha adquirido. Desde un punto de vista sincronizado, es obj.wait (), y obj.notify debe estar en el bloque de instrucción sincronizado (obj) {...}. Desde una perspectiva funcional, esperar significa que después de que el hilo adquiere el bloqueo del objeto, libera activamente el bloqueo del objeto y el hilo duerme. El bloqueo del objeto no se puede obtener y la ejecución continuará hasta que otro hilo llame al objeto notificar () para despertar el hilo. La notificación correspondiente () es la operación de activación del bloqueo del objeto. Pero una cosa a tener en cuenta es que después de la llamada notify (), el bloqueo del objeto no se libera de inmediato, pero la ejecución del bloque de instrucción sincronizado () {} {} se completa y el bloqueo se libera automáticamente, el JVM seleccionará aleatoriamente un subproceso del hilo de bloqueo de objeto Wait (), asigne al bloqueo del objeto, despertar el subproceso y continuará ejecución. Esto proporciona sincronización y operaciones de activación entre hilos. Tanto thread.sleep () y Object.Wait () pueden pausar el hilo actual y liberar el control de la CPU. La principal diferencia es que mientras Object.Wait () libera la CPU, libera el control del bloqueo del objeto.
No es suficiente comprender conceptualmente, y debe probarse en ejemplos prácticos para comprender mejor. El ejemplo más clásico de la aplicación de Object.Wait () y Object.notify () debería ser el problema de imprimir ABC con tres hilos. Esta es una pregunta de entrevista relativamente clásica, y las preguntas son las siguientes:
Establezca tres hilos, el hilo A imprima 10 veces, el hilo B imprime B 10 veces, el hilo C imprime C 10 veces, el hilo C requiere que se ejecute al mismo tiempo y ABC se imprime alternativamente 10 veces. Este problema se puede resolver fácilmente usando Wait () de Object y notificar (). El código es el siguiente:
/** * Uso de espera * @author dreamsea * @time 2015.3.9 */paquete com.multithread.wait; public class myThreadPrinter2 implementa runnable {name de cadena privada; objeto privado previo; objeto privado yo; privado mythreadprinter2 (nombre de cadena, objeto anterior, objeto self) {this.name = name; this.prev = prev; this.elf = self; } @Override public void run () {int count = 10; while (count> 0) {sincronizado (anterior) {sincronizado (self) {System.out.print (nombre); contar--; self.notify (); } try {Prev.Wait (); } catch (InterruptedException e) {E.PrintStackTrace (); }}}} public static void main (string [] args) lanza la excepción {objeto a = nuevo objeto (); Objeto b = nuevo objeto (); Objeto c = nuevo objeto (); MyThreadPrinter2 PA = new MyThreadPrinter2 ("A", C, A); MyThreadprinter2 pb = new MyThreadPrinter2 ("B", A, B); MyThreadPrinter2 PC = new MyThreadPrinter2 ("C", B, C); nuevo hilo (PA) .Start (); Hilt.sleep (100); // Asegúrese de ejecutar el nuevo hilo (pb) .Start (); Hilt.sleep (100); }} Resultado de salida:
Abcabcabcabcabcabcabcabcabcabcabc
Primero explicemos su idea general. Desde una perspectiva general, este problema es una operación de atención sincrónica entre tres hilos. El objetivo principal es ejecutar tres hilos en thread-> threadb-> threadc-> bucle thrave. Para controlar el orden de la ejecución del hilo, se debe determinar el orden de despertar y esperar, por lo que cada hilo debe contener dos bloqueos de objetos al mismo tiempo antes de que pueda continuar la ejecución. Un bloqueo de objeto es anterior, que es el bloqueo de objeto sostenido por el hilo anterior. Otro es el bloqueo del objeto. La idea principal es que para controlar el orden de ejecución, primero debe mantener el bloqueo anterior, es decir, el hilo anterior debe liberar su propio bloqueo de objeto y luego solicitar su propio bloqueo de objeto. Imprima cuando ambos son ambos. Luego, primero llame a Self.notify () para liberar su propio bloqueo de objeto, despierta el siguiente hilo de espera y luego llame a Prev.Wait () para liberar el bloqueo previo del objeto, termine el hilo actual y espere a que el bucle se despierte nuevamente. Ejecute el código anterior y puede encontrar que tres hilos imprimen ABC en un bucle, un total de 10 veces. El principal proceso de ejecución del programa es que el hilo A es el primero en ejecutar, contiene los bloqueos de objetos de C y A, y luego libera los bloqueos de A y C, y se despierta B. El subproceso B espera el bloqueo A, luego se aplica para el bloqueo B, luego imprime B, luego libera B, un bloqueo, Wakes C, Hilo C, espera el bloqueo B, luego se aplica para el bloqueo C, luego se imprime C, luego se imprime C, Bloqueo B, B Lock, y Bloqueo y WAKES A. A. No se aplica A. Lo piensas cuidadosamente, encontrarás que hay un problema, que es la condición inicial. Los tres hilos se inician en el orden de A, B y C. Según los pensamientos anteriores, A se despierta B, B despierta C, C y luego se despierta A. Sin embargo, esta suposición depende del orden de la programación y ejecución de los hilos en el JVM.
La diferencia entre espera y sueño
Puntos comunes:
1. Todos están en un entorno de múltiples subprocesos y pueden bloquear el número especificado de milisegundos en la llamada y devolución del programa.
2. Tanto Wait () como Sleep () pueden interrumpir el estado de pausa del hilo a través del método de interrupción (), de modo que el hilo inmediatamente arroja una Excepción InterruptedException.
Si el subproceso A quiere finalizar el hilo B inmediatamente, se puede llamar al método de interrupción en la instancia de hilo correspondiente al hilo B. Si el hilo B está esperando/dormir/unirse en este momento, el hilo B arrojará inmediatamente una intermediación interrupcada y la devolverá directamente a capt () {} para finalizar el hilo de forma segura.
Cabe señalar que el hilo en sí mismo se arroja la Excepción InterruptedException desde el interior, no por el método de interrupción (). Cuando se llama a Interrupt () en un hilo, si el hilo está ejecutando código normal, el hilo no arrojará una Excepción InterruptedException en absoluto. Sin embargo, una vez que el hilo ingrese a Wait ()/Sleep ()/Join (), se lanzará inmediatamente una InterruptException.
Diferencias:
1. Métodos de clase de hilo: sleep (), rendimiento (), etc.
Métodos del objeto: espera () y notificar (), etc.
2. Cada objeto tiene un bloqueo para controlar el acceso sincrónico. La palabra clave sincronizada puede interactuar con el bloqueo del objeto para realizar la sincronización de subprocesos.
El método de sueño no libera el bloqueo, mientras que el método de espera libera el bloqueo, para que otros hilos puedan usar bloques o métodos de control sincrónicos.
3. Espere, notifique y notifique todo solo se puede usar en métodos de control de sincronización o bloques de control de sincronización, mientras que el sueño se puede usar en cualquier lugar.
4. El sueño debe captar excepciones, mientras espere, notifique y notifique a todos los métodos no necesitan excepciones, por lo que la mayor diferencia entre los métodos de dormir () y esperar () es:
Cuando duerme () duerme, mantenga el objeto bloqueado y aún posee la cerradura;
Cuando Wait () duerme, se libera el bloqueo del objeto.
Sin embargo, Wait () y Sleep () pueden interrumpir el estado de pausa del hilo a través del método Interrupt (), de modo que el hilo inmediatamente arroje una EXPERECCIÓN INTERRUPTEDEX (pero no se recomienda usar este método).
método sleep ()
Sleep () hace que el hilo actual ingrese a un estado estancado (bloquea el hilo actual), renunciando al uso de la taza, y el propósito es evitar que el hilo actual ocupe los recursos de la CPU obtenidos solo por el proceso, para dejar un cierto tiempo para que otros hilos se ejecuten;
Sleep () es un método estático de la clase de hilo; Por lo tanto, no puede cambiar el bloqueo de la máquina del objeto, por lo que al llamar al método Sleep () en un bloque sincronizado, aunque el hilo está inactivo, el bloqueo de la máquina del objeto no se libera y otros hilos no pueden acceder al objeto (aunque todavía está conteniendo el bloqueo del objeto incluso cuando está dormido).
Después de que expira el tiempo de sueño para dormir () (), el hilo no necesariamente se ejecuta de inmediato, porque otros hilos pueden estar funcionando y no están programados para abandonar la ejecución a menos que el hilo tenga una prioridad más alta.
Método Wait ()
El método Wait () es un método en la clase de objeto; Cuando un hilo ejecuta el método Wait (), ingresa a un grupo de espera relacionado con el objeto, y al mismo tiempo pierde (liberas) el bloqueo de la máquina del objeto (pierde temporalmente el bloqueo de la máquina y el tiempo de tiempo de espera de espera (tiempo de espera largo) también necesitará devolver el bloqueo del objeto); Otros hilos pueden acceder a él;
Wait () usa notificar o notificar a todos los tiempo de sueño o especificado para despertar el hilo en la piscina actual de espera.
Wiat () debe colocarse en el bloque sincronizado, de lo contrario, la excepción "java.lang.illegalmonitorStateException" se lanzará cuando el tiempo de ejecución del programa.
7. Explicación de términos de hilo comunes
Hilo principal: el hilo generado por el programa de llamadas JVM Main ().
Hilo actual: este es un concepto confuso. Generalmente se refiere a un proceso obtenido a través de Thread.CurrentThread ().
Hilo de fondo: se refiere a un hilo que brinda servicios a otros hilos, también conocido como hilo de demonio. El hilo de recolección de basura JVM es un hilo de fondo. La diferencia entre un hilo de usuario y un hilo de demonio es si esperar a que el hilo principal finalice el hilo de primer plano, dependiendo del final del hilo principal: se refiere al hilo que acepta el servicio del hilo de fondo. De hecho, los hilos de fondo de primer plano están conectados juntos, al igual que la relación entre el títere y el manipulador detrás de escena. El títere es el hilo de primer plano, y el manipulador detrás de escena es el hilo de fondo. Los hilos creados por los hilos de primer plano también son hilos de primer plano de forma predeterminada. Puede usar métodos ISDAEMON () y SetDaemon () para determinar y establecer si un hilo es un hilo de fondo.
Algunos métodos comunes de clases de hilos:
Sleep (): Forra un hilo a dormir en N Millisegunds.
Isalive (): determina si un hilo sobrevive.
unir (): esperar a que el hilo termine.
activeCount (): el número de hilos activos en el programa.
Enumerate (): Enumerar hilos en el programa.
CurrentThread (): obtiene el hilo actual.
isdaemon (): si un hilo es un hilo de demonio.
setDaemon (): configure un hilo como un hilo de demonio. (La diferencia entre un hilo de usuario y un hilo de demonio es si esperar a que el hilo principal termine dependiendo del final del hilo principal)
setName (): Establezca un nombre para el hilo.
Wait (): Forra un hilo a esperar.
notificar (): notifique a un hilo para continuar ejecutándose.
setPriority (): establece la prioridad de un hilo.
8. Sincronización de hilos
1. Hay dos ámbitos de palabras clave sincronizadas:
1) Está dentro de una instancia de objeto. Amethod () {} sincronizado puede evitar que múltiples hilos accedan al método sincronizado de este objeto al mismo tiempo (si un objeto tiene múltiples métodos sincronizados, siempre que un hilo acceda a uno de los métodos sincronizados, otros hilos no pueden acceder a ningún método sincronizado en el objeto al mismo tiempo). En este momento, el método sincronizado de diferentes instancias de objetos es ininterrumpido. That is to say, other threads can still access the synchronized method in another object instance of the same class at the same time;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?还得对synchronized关键字的作用进行深入了解才可定论。
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA(){synchronized (this) // (1){ //…..}}(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private byte[] lock = new byte[0]; // 特殊的instance变量Public void methodA(){ synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo{public synchronized static void methodAAA() // 同步的static 函数{//….}public void methodBBB(){ synchronized(Foo.class) // class literal(类名称字面常量)} }代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将Foo.class和P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递
在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
9.1、通过构造方法传递数据在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:
package mythread; public class MyThread1 extends Thread { private String name; public MyThread1(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { Thread thread = new MyThread1("world"); thread.start(); }}由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
9.2、通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:
package mythread; public class MyThread2 implements Runnable { private String name; public void setName(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { MyThread2 myThread = new MyThread2(); myThread.setName("world"); Thread thread = new Thread(myThread); thread.start(); }} 9.3、通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。
package mythread; class Data { public int value = 0; } class Work { public void process(Data data, Integer numbers) { for (int n : numbers) { data.value += n; } } } public class MyThread3 extends Thread { private Work work; public MyThread3(Work work) { this.work = work; } public void run() { java.util.Random random = new java.util.Random(); Data data = new Data(); int n1 = random.nextInt(1000); int n2 = random.nextInt(2000); int n3 = random.nextInt(3000); work.process(data, n1, n2, n3); // Use the callback function System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" + String.valueOf(n3) + "=" + data.value); } public static void main(String[] args) { Thread thread = new MyThread3(new Work()); thread.start(); }}The above is a detailed explanation of Java multi-threading. I hope it can help you learn this part of the knowledge. ¡Gracias por su apoyo para este sitio web!