Ho bisogno di un generatore di byte che generi valori da Byte.MIN_VALUE a Byte.MAX_VALUE. Quando raggiunge MAX_VALUE, dovrebbe ricominciare da MIN_VALUE.Cosa c'è di sbagliato in questo generatore di sequenze di byte sicuro per i thread?
Ho scritto il codice utilizzando AtomicInteger (vedi sotto); tuttavia, sembra che il codice non si comporti correttamente se acceduto in modo concorrente e se reso artificialmente lento con Thread.sleep() (in caso contrario, funziona in modo corretto, tuttavia, sospetto che sia troppo veloce per la visualizzazione dei problemi di concorrenza).
Il codice (con un po 'di codice di debug aggiunto):
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;
}
}
E, il test:
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());
}
}
}
Il risultato sulla mia macchina 2-core è che il valore # -128 ottiene 146 colpi (tutti dovrebbero ottenere 100 hit uguali perché abbiamo 100 thread).
Se qualcuno ha qualche idea, cosa c'è di sbagliato in questo codice, sono tutto orecchie/occhi.
UPDATE: per coloro che sono di fretta e non vuole scorrere verso il basso, il modo corretto (e più breve e più elegante) per risolvere questo in Java sarebbe come questo:
public byte nextValue() {
return (byte) counter.incrementAndGet();
}
Grazie, Heinz!
Perché hai counter.incrementAndGet() nel blocco di sincronizzazione, che è come un incremento multiplo per tutto il thread che entra in quella sezione e successivamente viene reimpostato su "INITIAL_VALUE + 1" –