"Java efectivo" de Joshua Bloch, el elemento 51 no se trata de depender del programador de subprocesos, así como de no mantener los hilos innecesariamente en el estado ejecutable. El texto citado:Java Concurrency JDK 1.6: ¿La espera ocupada es mejor que la señalización? Java efectiva # 51
La principal técnica para mantener el número de hilos ejecutables abajo es tener cada hilo hacer una pequeña cantidad de trabajo y luego esperar a alguna condición usando Object.wait o por alguna tiempo que debe transcurrir usando Thread.sleep. Los subprocesos no deben estar ocupados, espere, verifique repetidamente una estructura de datos esperando que ocurra algo. Además de hacer que el programa sea vulnerable a los caprichos del planificador, la espera ocupada puede aumentar en gran medida la carga del procesador, , reduciendo la cantidad de trabajo útil que otros procesos pueden realizar en la misma máquina.
Y luego continúa para mostrar una microbanda de una espera ocupada frente al uso de señales correctamente. En el libro, la espera ocupada hace 17 viajes circulares/s mientras que la versión esperar/notificar hace 23,000 viajes ida y vuelta por segundo.
Sin embargo, cuando probé el mismo benchmark en JDK 1.6, veo todo lo contrario: la espera agitada hace 760K recorridos de ida/vuelta mientras que la espera/notificación arroja 53.3K recorridos de ida/vuelta, es decir, espera/notificación debería ha sido ~ 1400 veces más rápido, pero resulta ser ~ 13 veces más lento?
Entiendo que las esperas ocupadas no son buenas y la señalización es aún mejor: la utilización de la CPU es ~ 50% en la versión de espera ocupada, mientras que permanece en ~ 30% en la versión de espera/notificación, pero ¿hay algo que explique ¿los números?
Si ayuda, estoy ejecutando JDK1.6 (32 bit) en Win 7 x64 (core i5).
ACTUALIZACIÓN: Fuente a continuación. Para ejecutar el banco de trabajo ocupado, cambie la clase base de PingPongQueue a BusyWorkQueue import java.util.LinkedList; import java.util.List;
abstract class SignalWorkQueue {
private final List queue = new LinkedList();
private boolean stopped = false;
protected SignalWorkQueue() { new WorkerThread().start(); }
public final void enqueue(Object workItem) {
synchronized (queue) {
queue.add(workItem);
queue.notify();
}
}
public final void stop() {
synchronized (queue) {
stopped = true;
queue.notify();
}
}
protected abstract void processItem(Object workItem)
throws InterruptedException;
private class WorkerThread extends Thread {
public void run() {
while (true) { // Main loop
Object workItem = null;
synchronized (queue) {
try {
while (queue.isEmpty() && !stopped)
queue.wait();
} catch (InterruptedException e) {
return;
}
if (stopped)
return;
workItem = queue.remove(0);
}
try {
processItem(workItem); // No lock held
} catch (InterruptedException e) {
return;
}
}
}
}
}
// HORRIBLE PROGRAM - uses busy-wait instead of Object.wait!
abstract class BusyWorkQueue {
private final List queue = new LinkedList();
private boolean stopped = false;
protected BusyWorkQueue() {
new WorkerThread().start();
}
public final void enqueue(Object workItem) {
synchronized (queue) {
queue.add(workItem);
}
}
public final void stop() {
synchronized (queue) {
stopped = true;
}
}
protected abstract void processItem(Object workItem)
throws InterruptedException;
private class WorkerThread extends Thread {
public void run() {
final Object QUEUE_IS_EMPTY = new Object();
while (true) { // Main loop
Object workItem = QUEUE_IS_EMPTY;
synchronized (queue) {
if (stopped)
return;
if (!queue.isEmpty())
workItem = queue.remove(0);
}
if (workItem != QUEUE_IS_EMPTY) {
try {
processItem(workItem);
} catch (InterruptedException e) {
return;
}
}
}
}
}
}
class PingPongQueue extends SignalWorkQueue {
volatile int count = 0;
protected void processItem(final Object sender) {
count++;
SignalWorkQueue recipient = (SignalWorkQueue) sender;
recipient.enqueue(this);
}
}
public class WaitQueuePerf {
public static void main(String[] args) {
PingPongQueue q1 = new PingPongQueue();
PingPongQueue q2 = new PingPongQueue();
q1.enqueue(q2); // Kick-start the system
// Give the system 10 seconds to warm up
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
// Measure the number of round trips in 10 seconds
int count = q1.count;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println(q1.count - count);
q1.stop();
q2.stop();
}
}
Creo que eso es todo. Solo lo probé. Con un sueño de 1 ms introducido antes de colocar el elemento en la otra cola, ambas ejecuciones son prácticamente iguales: alrededor de 400 recorridos de ida y vuelta. Como se esperaba, la espera ocupada consume hasta 3 veces más de la CPU. ¡Gracias! – Raghu