Esta serie se basa en el curso de los números de refinación en el oro, y para aprender mejor, se hicieron una serie de registros. Este artículo presenta principalmente 1. ¿Qué es un hilo 2? Operaciones básicas de los hilos 3. Daemon Hild 4. Prioridad de hilo 5. Operaciones básicas de sincronización de hilos
1. ¿Qué es el hilo?
Los hilos son unidades de ejecución dentro de los procesos
Hay varios hilos en un proceso.
Un hilo es una unidad de ejecución dentro de un proceso.
La razón para usar hilos es que el cambio de proceso es una operación de peso muy pesado y consume recursos. Si usa múltiples procesos, el número de concurrencia no será muy alto. Los hilos son unidades de programación más pequeñas y son más ligeras, por lo que los hilos se usan más ampliamente en un diseño concurrente.
En Java, el concepto de hilos es similar al concepto de hilos a nivel de sistema operativo. De hecho, JVM asignará hilos en Java al área de subprocesos del sistema operativo.
2. Operaciones básicas de hilos
2.1 Diagrama de estado de hilo
La imagen de arriba muestra la operación básica de los hilos en Java.
Cuando un hilo es nuevo, el hilo en realidad no funciona. Simplemente genera una entidad, y el hilo en realidad se inicia cuando llama al método de inicio de esta instancia. Después del inicio, llega al estado ejecutable. Runnable significa que los recursos del hilo, etc., se han preparado y se pueden ejecutar, pero no significa que esté en el estado de ejecución. Debido a la rotación de rodajas de tiempo, el hilo puede no estar ejecutándose en este momento. Para nosotros, se puede considerar que el hilo ha sido ejecutado, pero si se ejecuta en realidad depende de la programación de la CPU física. Cuando se ejecuta la tarea del hilo, el hilo llega al estado terminado.
A veces, durante la ejecución de un hilo, es inevitable que se apliquen algunos bloqueos u monitores de objetos. Cuando no se puede recuperar, el hilo se bloqueará, se suspenderá y alcanza el estado bloqueado. Si este hilo llama al método de espera, está en un estado de espera. El hilo que ingresa al estado de espera esperará a que otros hilos lo notifiquen. Después de hacer la notificación, el estado de espera cambiará del estado de espera al estado ejecutable para continuar la ejecución. Por supuesto, hay dos tipos de estados de espera, uno es esperar indefinidamente hasta que se notifique. Ha estado esperando un período de tiempo limitado. Por ejemplo, si espera 10 segundos o no se ha notificado, cambiará automáticamente al estado ejecutable.
2.2 Crear un nuevo hilo
Thread Thread = new Thread ();
Thread.Start ();
Esto abre un hilo.
Una cosa a tener en cuenta es
Thread Thread = new Thread ();
thread.run ();
No puede abrir un nuevo hilo llamando directamente al método Ejecutar.
El método de inicio en realidad llama al método Ejecutar en un nuevo hilo del sistema operativo. En otras palabras, si llama al método Ejecutar directamente en lugar del método de inicio, no iniciará un nuevo hilo, pero realizará sus operaciones en la ejecución de llamadas de hilo actual.
Thread Thread = new Thread ("T1") {@Override public void run () {// TODO Método Generado automático stub System.out.println (thread.currentThread (). GetName ()); }}; thread.start (); Si se llama al inicio, la salida es t1thread thread = new Thread ("t1") {@Override public void run () {// TODO Método Generado automático stub.out.println (Thread.CurrentThread (). GetName ()); }}; thread.run (); Si se ejecuta, Main es la salida. (Turning Run directamente es en realidad solo una llamada de función ordinaria, y no ha logrado el papel de la múltiples subprocesos)
Hay dos formas de implementar el método de ejecución
El primer método es anular directamente el método Ejecutar. Como se muestra en el código en este momento, la forma más conveniente se puede lograr utilizando una clase anónima.
Thread Thread = new Thread ("T1") {@Override public void run () {// TODO Método Generado automático stub System.out.println (thread.currentThread (). GetName ()); }}; El segundo camino
Hilo t1 = nuevo hilo (new CreateThread3 ());
Createthread3 () implementa la interfaz runnable.
En el video de Zhang Xiaoxiang, se recomienda el segundo método, diciendo que está más orientado a objetos.
2.3 Terminar hilo
No se recomienda thread.stop (). Libera todos los monitores
Se ha establecido claramente en el código fuente que el método de parada está en desuso, y la razón también se explica en Javadoc.
La razón es que el método de parada es demasiado "violento". No importa dónde se ejecute el hilo, dejará de soltar inmediatamente el hilo.
Cuando el hilo de escritura obtiene el bloqueo, comienza a escribir datos. Después de escribir ID = 1, se detiene y el bloqueo se libera cuando se prepara para establecer el nombre = 1. El hilo de lectura obtiene el bloqueo para la operación de lectura. La lectura de ID es 1 y el nombre sigue siendo 0, lo que causa inconsistencia de datos.
Lo más importante es que este tipo de error no arrojará una excepción y será difícil de detectar.
2.4 interrupción de hilo
Hay 3 formas de interrumpir el hilo
public void thread.interrupt () // hilo de interrupción
público boolean hilo.isinterrupted () // Determinar si está interrumpido
public static static boolean thread.interrupted () // Determinar si se interrumpe y borra el estado de interrupción actual
¿Qué es la interrupción del hilo?
Si no comprende el mecanismo de interrupción de Java, es muy probable que tal explicación cause malentendidos, y cree que llamar al método de interrupción del hilo definitivamente interrumpirá el hilo.
De hecho, la interrupción de Java es un mecanismo de colaboración. En otras palabras, llamar al método de interrupción de un objeto de hilo no necesariamente interrumpe el hilo en ejecución, solo requiere que el hilo se interrumpa en el momento adecuado. Cada hilo tiene un estado de interrupción booleana (no necesariamente la propiedad del objeto, de hecho, este estado no es un campo de hilo), y el método de interrupción simplemente establece el estado en verdadero. Para los hilos que no son de bloqueo, el estado de interrupción se cambia, es decir, Thread.Isinterrupted () devolverá verdadero y no detendrá el programa;
public void run () {// Thread t1 while (true) {thread.yield (); }} t1.interrupt (); Esto no tendrá ningún efecto para interrumpir el hilo T1, pero el bit de estado de interrupción se cambia.
Si quieres terminar este hilo con mucha gracia, debes hacer esto
public void run () {while (true) {if (thread.currentThread (). isinterrupted ()) {System.out.println ("Interruted!"); romper; } Thread.yield (); }}El uso de interrupciones proporciona ciertas garantías para la consistencia de los datos.
Para hilos en estados de bloqueo cancelable, como hilos que esperan en estas funciones, hilt.sleep (), object.wait () y thread.Join (), este hilo arrojará una Excepción InterruptedException después de recibir la señal de interrupción, y volverá a colocar el estado de interrupción en falso.
Para hilos en estados de desbloqueo, puede escribir código como este:
public void run () {while (true) {if (thread.currentThread (). isinterrupted ()) {System.out.println ("Interruted!"); romper; } try {Thread.sleep (2000); } catch (InterruptedException e) {System.out.println ("Interrutado cuando duerme"); // Establecer el estado de interrupción. El bit de la bandera de interrupción se borrará después de lanzar un hilo de excepción. CurrentThread (). Interrupt (); } Thread.yield (); }}2.5 colgaciones de hilo
Suspender y reanudar hilos
suspender () no liberará el bloqueo
Si el bloqueo se produce antes del reanudar (), ambos métodos de punto muerto que ocurren son métodos desaprobados y no se recomiendan.
La razón es que Suspend no libera el bloqueo, por lo que ningún hilo puede acceder al recurso de área crítica que está bloqueada por él hasta que se reanude por otros hilos. Debido a que la secuencia de hilos en ejecución no se puede controlar, si el método de currículum de otros hilos se ejecuta primero, el suspendido más tarde siempre ocupará la cerradura, lo que provoca un punto muerto.
Use el siguiente código para simular este escenario
prueba de paquete; prueba de clase pública {objeto estático u = nuevo objeto (); static testSuspendThread t1 = new testSuspendThread ("t1"); static testSuspendThread t2 = new testSuspendThread ("t2"); public static class testSuspendThread extiende el hilo {public testSuspendThread (name de cadena) {setName (name); } @Override public void run () {SynChronized (U) {System.out.println ("en" + getName ()); Thread.CurrentThread (). Suspend (); }} public static void main (string [] args) lanza interruptedException {t1.start (); Hilt.sleep (100); t2.start (); t1.resume (); t2.resume (); t1.Join (); t2.join (); }} Deje que T1 y T2 compitan por un bloqueo al mismo tiempo, el hilo que se compite está suspendido, y luego reanude. Hablando lógicamente, el currículum debe lanzar un hilo después de competir por él, y luego otro hilo compite por el bloqueo y los currículums.
La salida del resultado es:
en T1
en T2
Esto significa que ambos hilos compiten por el bloqueo, pero la luz roja en la consola aún está encendida, lo que significa que debe haber hilos que no se han ejecutado en T1 y T2. Vamos a dejarlo y echar un vistazo
Se descubrió que T2 ha sido suspendido. Esto crea un punto muerto.
2.6 Únete y Yeild
Yeild es un método estático nativo. Este método es liberar el tiempo de la CPU que posee y luego competir con otros hilos (tenga en cuenta que los hilos de Yeild aún pueden competir por la CPU, prestar atención a la diferencia del sueño). También se explica en Javadoc que Yeild es un método que básicamente no se usa y generalmente se usa en depuración y prueba.
El método de unión significa esperar a que terminen otros hilos, al igual que el código en la sección de suspensión, desea que el hilo principal espere a que T1 y T2 terminen antes de terminar. Si no termina, el hilo principal se bloqueará allí.
prueba de paquete; prueba de clase pública {public volátil estática int i = 0; public static class addthread extiende el hilo {@Override public void run () {for (i = 0; i <10000000; i ++); }} public static void main (string [] args) lanza interruptedException {addThread AT = new AddThread (); at.Start (); at.Join (); System.out.println (i); }} Si se elimina el AT.JOIN del código anterior, el hilo principal se ejecutará directamente y el valor de I será muy pequeño. Si hay una unión, el valor del impreso debe ser 10000000.
Entonces, ¿cómo se implementa unirse?
La naturaleza de unión
mientras (isalive ())
{
esperar (0);
}
El método Join () también puede pasar un tiempo, lo que significa esperar un período de tiempo limitado, y despertarse automáticamente después de este tiempo.
Hay una pregunta como esta: ¿Quién notificará al hilo? ¿No hay lugar para llamar a la llamada en la clase de hilo?
En Javadoc, se encontró una explicación relevante. Cuando se complete y termina un hilo, se llamará al método NotifyAll para despertar todos los hilos que esperan en la instancia de hilo actual. Esta operación es realizada por JVM.
Por lo tanto, Javadoc también nos dio una sugerencia de no usar Wait y Notify/Notifyall en las instancias de hilo. Debido a que JVM se llamará a sí mismo, puede ser diferente del resultado esperado de su llamada.
3. Hilo de demonio
Completar silenciosamente algunos servicios sistemáticos en el fondo, como hilos de recolección de basura y hilos JIT, se puede entender como hilos de demonio.
Cuando todos los procesos que no son de Demonia terminan dentro de una aplicación Java, la máquina virtual Java saldrá de forma natural.
He escrito un artículo sobre cómo implementarlo en Python, échale un vistazo aquí.
Es relativamente simple convertirse en un demonio en Java.
Hilo t = nuevo daemont ();
T.SetDaemon (verdadero);
t.Start ();
Esto abre un hilo de demonio.
prueba de paquete; prueba de clase pública {public static class DaemonThread extiende el hilo {@Override public void run () {for (int i = 0; i <10000000; i ++) {system.out.println ("hi"); }}} public static void main (string [] args) lanza interruptedException {DaemonThread dt = new DaemonThread (); dt.start (); }} Cuando el subproceso DT no es un hilo de demonio, después de ejecutar, podemos ver la salida de la consola hola
Al unirse antes de comenzar
dt.setdaemon (verdadero);
La consola sale directamente y no tiene salida.
4. Prioridad del hilo
Hay 3 variables en la clase de hilo que definen la prioridad del hilo.
Public Final Static int min_priority = 1; public final static int norm_priority = 5; public final static int max_priority = 10; prueba de paquete; prueba de clase pública {public static class High extiende hilo {static int count = 0; @Override public void run () {while (true) {sincronizado (test.class) {count ++; if (Count> 10000000) {System.out.println ("High"); romper; }}}}} public static clase estática baja extiende hilo {static int count = 0; @Override public void run () {while (true) {sincronizado (test.class) {count ++; if (Count> 10000000) {System.out.println ("Low"); romper; }}}}} public static void main (string [] args) lanza interruptedException {high high = new high (); Bajo bajo = nuevo bajo (); High.SetPriority (hilo.max_priority); Low.setPriority (hilo.min_priority); Low.Start (); high.Start (); }} Deje que un hilo de alta prioridad y un hilo de baja prioridad compitan por un bloqueo al mismo tiempo y vea cuál se completa primero.
Por supuesto, no necesariamente tiene que completarse primero con alta prioridad. Después de ejecutarlo varias veces, descubrí que la probabilidad de completar de alta prioridad es relativamente alta, pero aún es posible completarlo primero con baja prioridad.
5. Operación de sincronización de hilos básicos
sincronizado y object.wait () objct.notify ()
Para obtener detalles de esta sección, consulte un blog que escribí antes.
Lo principal a tener en cuenta es
Hay tres formas de bloquear sincronizado:
Especifique el objeto bloqueado: bloquee el objeto dado y obtenga el bloqueo del objeto dado antes de ingresar el código de sincronización.
Actuando directamente sobre el método de instancia: es equivalente a bloquear la instancia actual. Antes de ingresar el código de sincronización, debe obtener el bloqueo de la instancia actual.
Actuando directamente sobre métodos estáticos: es equivalente a bloquear la clase actual. Antes de ingresar el código sincrónico, debe obtener el bloqueo de la clase actual.
Si funciona en el método de instancia, no nuevas instancias diferentes
Funciona en métodos estáticos, siempre que la clase sea la misma, porque el bloqueo agregado es una clase. Clase, y dos instancias diferentes pueden ser nuevas.
Cómo usar Wait and Notify:
Use cualquier bloqueo, llame Wait y notifique
No entraré en detalles en este artículo.