Necesito un generador de bytes que genere valores desde Byte.MIN_VALUE a Byte.MAX_VALUE. Cuando alcanza MAX_VALUE, debería comenzar de nuevo desde MIN_VALUE.¿Qué hay de malo con este generador de secuencia de bytes seguro para subprocesos?
He escrito el código usando AtomicInteger (ver a continuación); sin embargo, el código no parece comportarse correctamente si se accede simultáneamente y si se hace artificialmente lento con Thread.sleep() (si no duerme, funciona bien, sin embargo, sospecho que es demasiado rápido para que aparezcan los problemas de concurrencia).
El código (con algo de código de depuración añadido):
public class ByteGenerator {
private static final int INITIAL_VALUE = Byte.MIN_VALUE-1;
private AtomicInteger counter = new AtomicInteger(INITIAL_VALUE);
private AtomicInteger resetCounter = new AtomicInteger(0);
private boolean isSlow = false;
private long startTime;
public byte nextValue() {
int next = counter.incrementAndGet();
//if (isSlow) slowDown(5);
if (next > Byte.MAX_VALUE) {
synchronized(counter) {
int i = counter.get();
//if value is still larger than max byte value, we reset it
if (i > Byte.MAX_VALUE) {
counter.set(INITIAL_VALUE);
resetCounter.incrementAndGet();
if (isSlow) slowDownAndLog(10, "resetting");
} else {
if (isSlow) slowDownAndLog(1, "missed");
}
next = counter.incrementAndGet();
}
}
return (byte) next;
}
private void slowDown(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
private void slowDownAndLog(long millis, String msg) {
slowDown(millis);
System.out.println(resetCounter + " "
+ (System.currentTimeMillis()-startTime) + " "
+ Thread.currentThread().getName() + ": " + msg);
}
public void setSlow(boolean isSlow) {
this.isSlow = isSlow;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
}
Y, la prueba:
public class ByteGeneratorTest {
@Test
public void testGenerate() throws Exception {
ByteGenerator g = new ByteGenerator();
for (int n = 0; n < 10; n++) {
for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
assertEquals(i, g.nextValue());
}
}
}
@Test
public void testGenerateMultiThreaded() throws Exception {
final ByteGenerator g = new ByteGenerator();
g.setSlow(true);
final AtomicInteger[] counters = new AtomicInteger[Byte.MAX_VALUE-Byte.MIN_VALUE+1];
for (int i = 0; i < counters.length; i++) {
counters[i] = new AtomicInteger(0);
}
Thread[] threads = new Thread[100];
final CountDownLatch latch = new CountDownLatch(threads.length);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
try {
for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
byte value = g.nextValue();
counters[value-Byte.MIN_VALUE].incrementAndGet();
}
} finally {
latch.countDown();
}
}
}, "generator-client-" + i);
threads[i].setDaemon(true);
}
g.setStartTime(System.currentTimeMillis());
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
latch.await();
for (int i = 0; i < counters.length; i++) {
System.out.println("value #" + (i+Byte.MIN_VALUE) + ": " + counters[i].get());
}
//print out the number of hits for each value
for (int i = 0; i < counters.length; i++) {
assertEquals("value #" + (i+Byte.MIN_VALUE), threads.length, counters[i].get());
}
}
}
El resultado en mi máquina de 2 hilos es que el valor # -128 obtiene 146 golpes (todos ellos deberían obtener 100 hits por igual ya que tenemos 100 hilos).
Si alguien tiene alguna idea, ¿qué hay de malo en este código? Soy todo oídos/ojos.
ACTUALIZACIÓN: para los que tienen prisa y no quieren que desplazarse hacia abajo, la forma correcta (y la más corta y la más elegante) para resolver esto en Java sería así:
public byte nextValue() {
return (byte) counter.incrementAndGet();
}
Gracias, Heinz!
¿Por qué tiene counter.incrementAndGet() en el bloque de sincronización, que es como un incremento múltiple para todo el hilo que entra en esa sección y luego se restablece a "INITIAL_VALUE + 1" –