Hay algo extraño acerca de la implementación del BoundedExecutor
en el libro Java Concurrency in Practice.Concurrencia de Java en la práctica: condición de carrera en BoundedExecutor?
Se supone que debe estrangular el envío de tareas al Ejecutor al bloquear el hilo de envío cuando hay suficientes hilos en cola o ejecutándose en el Ejecutor.
Esta es la aplicación (después de añadir el volver a lanzar falta en la cláusula catch):
public class BoundedExecutor {
private final Executor exec;
private final Semaphore semaphore;
public BoundedExecutor(Executor exec, int bound) {
this.exec = exec;
this.semaphore = new Semaphore(bound);
}
public void submitTask(final Runnable command) throws InterruptedException, RejectedExecutionException {
semaphore.acquire();
try {
exec.execute(new Runnable() {
@Override public void run() {
try {
command.run();
} finally {
semaphore.release();
}
}
});
} catch (RejectedExecutionException e) {
semaphore.release();
throw e;
}
}
Cuando una instancia del BoundedExecutor
con un Executors.newCachedThreadPool()
y un límite de 4, yo esperaría que el número de hilos instanciados por el grupo de subprocesos en caché nunca excederá 4. En la práctica, sin embargo, sí lo hace. He conseguido este pequeño programa de prueba para crear hasta 11 hilos:
public static void main(String[] args) throws Exception {
class CountingThreadFactory implements ThreadFactory {
int count;
@Override public Thread newThread(Runnable r) {
++count;
return new Thread(r);
}
}
List<Integer> counts = new ArrayList<Integer>();
for (int n = 0; n < 100; ++n) {
CountingThreadFactory countingThreadFactory = new CountingThreadFactory();
ExecutorService exec = Executors.newCachedThreadPool(countingThreadFactory);
try {
BoundedExecutor be = new BoundedExecutor(exec, 4);
for (int i = 0; i < 20000; ++i) {
be.submitTask(new Runnable() {
@Override public void run() {}
});
}
} finally {
exec.shutdown();
}
counts.add(countingThreadFactory.count);
}
System.out.println(Collections.max(counts));
}
Creo que hay un pequeño marco de tiempo entre la liberación del semáforo y la terminación de tareas, donde otro hilo puede adquirir un permiso y envíe una tarea mientras el hilo de liberación aún no ha terminado. En otras palabras, tiene una condición de carrera.
¿Alguien puede confirmar esto?
Agregué un 1ms Thread.sleep justo después del semáforo.release() para ver cuánto peor iba a obtener: obtuve más de 300 hilos creados. – toto2