sincronizacion - thread java
¿Cuáles son los principales usos de yield() y cómo difiere de join() y de interruption()? (10)
Estoy un poco confundido sobre el uso del método yield()
en Java, específicamente en el siguiente código de ejemplo. También he leído que yield () se ''usa para evitar la ejecución de un hilo''.
Mis preguntas son:
Creo que el código siguiente produce el mismo resultado tanto al usar
yield()
como cuando no se usa. ¿Es esto correcto?¿Cuáles son, de hecho, los principales usos del
yield()
?¿De qué forma es
yield()
diferente de los métodosjoin()
yinterrupt()
?
El ejemplo del código:
public class MyRunnable implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
for(int i=0; i<5; i++) {
System.out.println("Inside main");
}
}
public void run() {
for(int i=0; i<5; i++) {
System.out.println("Inside run");
Thread.yield();
}
}
}
Obtengo el mismo resultado usando el código anterior tanto con y sin usar yield()
:
Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
¿Cuáles son, de hecho, los principales usos del rendimiento ()?
El rendimiento sugiere a la CPU que puede detener el hilo actual y comenzar a ejecutar los hilos con mayor prioridad. En otras palabras, asignar un valor de baja prioridad al hilo actual para dejar espacio para más hilos críticos.
Creo que el código siguiente produce el mismo resultado tanto al usar yield () como cuando no se usa. ¿Es esto correcto?
NO, los dos producirán resultados diferentes. Sin un rendimiento (), una vez que el hilo obtiene el control, ejecutará el ciclo ''Inside run'' de una vez. Sin embargo, con un rendimiento (), una vez que el hilo obtiene el control, imprimirá la ''Ejecución interna'' una vez y luego transferirá el control a otra secuencia, si existe. Si no hay ningún hilo pendiente, este hilo se reanudará nuevamente. Por lo tanto, cada vez que se ejecuta "Ejecución interna" buscará otros subprocesos para ejecutar y, si no hay ningún hilo disponible, el hilo actual seguirá ejecutándose.
¿De qué forma es yield () diferente de los métodos join () y interrupt ()?
yield () es para dar cabida a otros hilos importantes, join () es para esperar que otro subproceso complete su ejecución, y interrupt () es para interrumpir un subproceso que se está ejecutando actualmente para hacer algo más.
1. Con y sin utilizar el método de rendimiento en el código siguiente, se obtiene la misma salida. ¿Es correcto?
El resultado correcto en este caso no se especifica, por lo que todo es correcto.
2. En realidad, cuál es el uso principal del rendimiento ()
Casi nunca se usa, y mucho menos para identificar un "uso principal". Nunca he usado un método yield () en ninguna plataforma, y los tengo a mi disposición desde hace más de 20 años, y me he involucrado en una programación del sistema bastante seria durante todo ese período.
3.Cómo es diferente de los métodos join () e interrupt ()
Es diferente porque no es lo mismo. La pregunta es completamente sin sentido.
La (s) respuesta (s) actual (es) están desactualizadas y requieren revisión dados los cambios recientes.
No hay diferencia práctica de Thread.yield()
entre las versiones de Java desde 6 hasta 9.
TL; DR;
Conclusiones basadas en el código fuente de OpenJDK ( http://hg.openjdk.java.net/ ).
Si no se tiene en cuenta el soporte HotSpot de las sondas USDT (la información de seguimiento del sistema se describe en la guía dtrace ) y la propiedad JVM ConvertYieldToSleep
, el código fuente de yield()
es casi el mismo. Vea la explicación a continuación.
Java 9 :
Thread.yield()
llama al método OS específico os::naked_yield()
:
En Linux:
void os::naked_yield() {
sched_yield();
}
En Windows:
void os::naked_yield() {
SwitchToThread();
}
Java 8 y anterior:
Thread.yield()
llama al método específico del sistema operativo os::yield()
:
En Linux:
void os::yield() {
sched_yield();
}
En Windows:
void os::yield() { os::NakedYield(); }
Como puede ver, Thread.yeald()
en Linux es idéntico para todas las versiones de Java.
Veamos el os::NakedYield()
de Windows de JDK 8:
os::YieldResult os::NakedYield() {
// Use either SwitchToThread() or Sleep(0)
// Consider passing back the return value from SwitchToThread().
if (os::Kernel32Dll::SwitchToThreadAvailable()) {
return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
} else {
Sleep(0);
}
return os::YIELD_UNKNOWN ;
}
La diferencia entre Java 9 y Java 8 en la comprobación adicional de la existencia del método SwitchToThread()
de la API de Win32. El mismo código está presente para Java 6.
El código fuente de os::NakedYield()
en JDK 7 es ligeramente diferente pero tiene el mismo comportamiento:
os::YieldResult os::NakedYield() {
// Use either SwitchToThread() or Sleep(0)
// Consider passing back the return value from SwitchToThread().
// We use GetProcAddress() as ancient Win9X versions of windows doen''t support SwitchToThread.
// In that case we revert to Sleep(0).
static volatile STTSignature stt = (STTSignature) 1 ;
if (stt == ((STTSignature) 1)) {
stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
// It''s OK if threads race during initialization as the operation above is idempotent.
}
if (stt != NULL) {
return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
} else {
Sleep (0) ;
}
return os::YIELD_UNKNOWN ;
}
La comprobación adicional se ha eliminado debido a que el método SwitchToThread()
está disponible desde Windows XP y Windows Server 2003 (consulte las notas msdn ).
Acerca de las diferencias entre yield()
, interrupt()
y join()
- en general, no solo en Java:
- ceder : Literalmente, ''ceder'' significa dejar ir, rendirse, rendirse. Un hilo de rendimiento le dice al sistema operativo (o la máquina virtual, o no) que está dispuesto a permitir que otros hilos se programen en su lugar. Esto indica que no está haciendo algo demasiado crítico. Sin embargo, es solo una pista y no garantiza que tenga ningún efecto.
- unión : cuando varios hilos ''se unen'' en algún manejador, token o entidad, todos ellos esperan hasta que todos los otros hilos relevantes hayan completado la ejecución (por completo o hasta su propia unión correspondiente). Eso significa que un grupo de hilos han completado sus tareas. Entonces, cada uno de estos hilos puede programarse para continuar otro trabajo, pudiendo suponer que todas esas tareas están realmente completas. (¡No debe confundirse con SQL Joins!)
- interrupción : utilizada por un hilo para ''insertar'' otro hilo que está en reposo, o en espera, o en unión, de modo que está programado para continuar ejecutándose nuevamente, tal vez con una indicación de que ha sido interrumpido. (¡No debe confundirse con interrupciones de hardware!)
Para Java específicamente, vea
Unión:
¿Cómo usar Thread.join? (aquí en )
Flexible:
Interrumpiendo:
¿Es Thread.interrupt () evil? (aquí en )
El uso principal de yield () es para poner una aplicación multihilo en espera.
todas estas diferencias de métodos son yield () pone el hilo en espera mientras ejecuta otro hilo y regresa después de completarlo, join () traerá el comienzo de los hilos ejecutando juntos hasta el final y de otro hilo para ejecutar después de que el hilo tenga finalizado, interrupt () detendrá la ejecución de un hilo por un tiempo.
Primero, la descripción real es
Hace que el objeto de ejecución actualmente en ejecución pause temporalmente y permita que se ejecuten otros subprocesos.
Ahora, es muy probable que su subproceso principal ejecute el ciclo cinco veces antes de que se run
método de run
del nuevo subproceso, por lo que todas las llamadas a yield
sucederán solo después de que se ejecute el ciclo en el subproceso principal.
join
detendrá el hilo actual hasta que el hilo invocado con join()
termine de ejecutarse.
interrupt
interrumpirá el hilo al que se está llamando, causando InterruptedException .
yield
permite un cambio de contexto a otros hilos, por lo que este hilo no consumirá todo el uso de la CPU del proceso.
Thread.yield ()
Cuando invocamos el método Thread.yield (), el programador de subprocesos mantiene el hilo actualmente en ejecución en estado Ejecutable y selecciona otro hilo de igual prioridad o mayor prioridad. Si no hay un subproceso de prioridad igual o superior, reprogramar el subproceso yield () de llamada. Recuerde que el método de rendimiento no hace que el hilo vaya al estado Esperar o Bloqueado. Solo puede hacer que un hilo pase de Estado en ejecución a Estado ejecutable.
unirse()
Cuando una instancia de subproceso llama a join, este subproceso indicará al subproceso en ejecución que espere hasta que finalice el subproceso de unión. Unirse se usa en las situaciones en las que una tarea debe completarse antes de que la tarea actual termine.
Veo que la pregunta se ha reactivado con una recompensa, ahora preguntándome cuáles son los usos prácticos para el yield
. Daré un ejemplo de mi experiencia.
Como sabemos, el yield
obliga a la cadena de llamada a renunciar al procesador en el que se está ejecutando para poder programar la ejecución de otro subproceso. Esto es útil cuando el hilo actual ha terminado su trabajo por el momento, pero quiere regresar rápidamente al frente de la cola y verificar si alguna condición ha cambiado. ¿Cómo es esto diferente de una variable de condición? yield
permite que el hilo regrese mucho más rápido a un estado de ejecución. Cuando se espera una variable de condición, el hilo se suspende y debe esperar que aparezca un hilo diferente para indicar que debe continuar. yield
básicamente dice "permitir que se ejecute un hilo diferente, pero me permite volver a trabajar muy pronto, ya que espero que algo cambie en mi estado muy rápidamente". Esto apunta hacia el giro ocupado, donde una condición puede cambiar rápidamente, pero suspender el hilo incurriría en un gran golpe de rendimiento.
Pero suficiente balbuceo, aquí hay un ejemplo concreto: el patrón paralelo del frente de onda. Una instancia básica de este problema es calcular las "islas" individuales de 1s en una matriz bidimensional llena de 0s y 1s. Una "isla" es un grupo de celdas adyacentes entre sí, ya sea vertical u horizontalmente:
1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1
Aquí tenemos dos islas de 1: arriba a la izquierda y abajo a la derecha.
Una solución simple es hacer una primera pasada sobre toda la matriz y reemplazar los valores de 1 con un contador de incremento de tal manera que al final cada 1 se reemplazó con su número de secuencia en orden principal de fila:
1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8
En el siguiente paso, cada valor se reemplaza por el mínimo entre él y los valores de sus vecinos:
1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4
Ahora podemos determinar fácilmente que tenemos dos islas.
La parte que queremos ejecutar en paralelo es el paso donde calculamos los mínimos. Sin entrar en demasiados detalles, cada subproceso obtiene filas intercaladas y se basa en los valores calculados por el subproceso que procesa la fila anterior. Por lo tanto, cada subproceso necesita retrasarse ligeramente detrás del subproceso que procesa la línea anterior, pero también debe mantenerse dentro de un tiempo razonable. En este documento, presento más detalles y una implementación. Tenga en cuenta el uso de sleep(0)
que es más o menos el equivalente en C de yield
.
En este caso, se utilizó el yield
para forzar a cada hilo a pausar, pero dado que el procesamiento del hilo, la fila adyacente avanzaría muy rápido mientras tanto, una variable de condición sería una elección desastrosa.
Como puede ver, el yield
es una optimización bastante fina. Usarlo en el lugar equivocado, por ejemplo, esperar en una condición que cambia raramente, causará un uso excesivo de la CPU.
Perdón por el largo parloteo, espero haber sido claro.
Thread.yield()
hace que el hilo pase del estado "En ejecución" al estado "Ejecutable". Nota: No hace que el hilo pase al estado "En espera".
Fuente: http://www.javamex.com/tutorials/threads/yield.shtml
Windows
En la implementación de Hotspot, la forma en que funciona
Thread.yield()
ha cambiado entre Java 5 y Java 6.En Java 5,
Thread.yield()
llama a la API de Windows llamadaSleep(0)
. Esto tiene el efecto especial de borrar el cuanto del hilo actual y ponerlo al final de la cola para su nivel de prioridad . En otras palabras, todos los subprocesos ejecutables de la misma prioridad (y los de mayor prioridad) tendrán la oportunidad de ejecutarse antes de que el subproceso entregado sea luego dado al tiempo de CPU. Cuando finalmente se vuelva a programar, volverá con un cuanto completo completo , pero no "transferirá" ninguno de los valores cuánticos restantes desde el momento de ceder. Este comportamiento es un poco diferente de un sueño distinto de cero donde el hilo de dormir generalmente pierde 1 valor cuántico (en efecto, 1/3 de un tic de 10 o 15 ms).En Java 6, este comportamiento fue cambiado. El Hotspot VM ahora implementa
Thread.yield()
usando la llamada a la API WindowsSwitchToThread()
. Esta llamada hace que el hilo actual pierda su tiempo actual , pero no su quantum completo. Esto significa que, dependiendo de las prioridades de otros hilos, el hilo que cede se puede programar en un período de interrupción posterior . (Consulte la sección sobre programación de subprocesos para obtener más información sobre timeslices).Linux
En Linux, Hotspot simplemente llama a
sched_yield()
. Las consecuencias de esta llamada son un poco diferentes, y posiblemente más severas que en Windows:
- un hilo entregado no obtendrá otro trozo de CPU hasta que todos los otros hilos hayan tenido una porción de CPU ;
- (al menos en Kernel 2.6.8 en adelante), el hecho de que el hilo ha sido producido es tomado en cuenta implícitamente por la heurística del planificador en su reciente asignación de CPU; por lo tanto, implícitamente, un hilo que haya cedido podría recibir más CPU cuando esté programado en el futuro.
(Consulte la sección de programación de subprocesos para obtener más detalles sobre las prioridades y los algoritmos de programación).
Cuándo usar
yield()
?Yo diría que prácticamente nunca . Su comportamiento no está definido de forma estándar y, en general, hay mejores formas de realizar las tareas que puede realizar con yield ():
- si intentas usar solo una parte de la CPU , puedes hacerlo de una manera más controlable al estimar la cantidad de CPU que el hilo ha utilizado en su último tramo de procesamiento, y luego dormir durante un cierto tiempo para compensar: ver el método sleep() ;
- Si está esperando que un proceso o recurso finalice o esté disponible, hay formas más eficientes de lograr esto, como usar join() para esperar que se complete otro subproceso, usando el mecanismo de wait/notify para permitir un hilo para indicar a otro que una tarea está completa, o idealmente utilizando una de las construcciones de simultaneidad de Java 5, como un Semaphore o cola de bloqueo .