java – 这个线程安全的字节序列生成器有什么问题?

前端之家收集整理的这篇文章主要介绍了java – 这个线程安全的字节序列生成器有什么问题?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我需要一个字节生成器,它将生成从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;
}

猜你在找的Java相关文章