我需要一个字节生成器,它将生成从Byte.MIN_VALUE到Byte.MAX_VALUE的值.当它达到MAX_VALUE时,它应该从MIN_VALUE重新开始.
我使用AtomicInteger编写了代码(见下文);但是,如果同时访问并且如果使用Thread.sleep()人为地减慢了代码,那么代码似乎没有正常运行(如果没有睡眠,它运行正常;但是,我怀疑它对于出现并发问题来说太快了).
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; } }
并且,测试:
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()); } } }
在我的2核机器上的结果是值#-128得到146次点击(所有这些都应该得到100次点击,因为我们有100个线程).
如果有人有任何想法,这个代码有什么问题,我都是耳朵/眼睛.
更新:对于那些赶时间并且不想向下滚动的人来说,在Java中解决这个问题的正确(以及最短和最优雅)方式将是这样的:
public byte nextValue() { return (byte) counter.incrementAndGet(); }
谢谢,亨氏!
解决方法
您根据counter.get()的旧值决定incrementAndGet().在对计数器执行incrementAndGet()操作之前,计数器的值可以再次达到MAX_VALUE.
if (next > Byte.MAX_VALUE) { synchronized(counter) { int i = counter.get(); //here You make sure the the counter is not over the MAX_VALUE if (i > Byte.MAX_VALUE) { counter.set(INITIAL_VALUE); resetCounter.incrementAndGet(); if (isSlow) slowDownAndLog(10,"resetting"); } else { if (isSlow) slowDownAndLog(1,"missed"); //the counter can reach MAX_VALUE again if you wait here long enough } next = counter.incrementAndGet(); //here you increment on return the counter that can reach >MAX_VALUE in the meantime } }
为了使其工作,必须确保不对陈旧信息做出决定.重置计数器或返回旧值.
public byte nextValue() { int next = counter.incrementAndGet(); if (next > Byte.MAX_VALUE) { synchronized(counter) { next = counter.incrementAndGet(); //if value is still larger than max byte value,we reset it if (next > Byte.MAX_VALUE) { counter.set(INITIAL_VALUE + 1); next = INITIAL_VALUE + 1; resetCounter.incrementAndGet(); if (isSlow) slowDownAndLog(10,"resetting"); } else { if (isSlow) slowDownAndLog(1,"missed"); } } } return (byte) next; }