简体   繁体   中英

How use long instead of int can bulletproof the following method - Effective Java

Consider the following code picked from Joshua Bloch - Effective Java, page 263

// Broken - requires synchronization!
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
    return nextSerialNumber++;
}

One way to fix the generateSerialNumber method is to add the synchronized modifier to its declaration. This ensures that multiple invocations won't be interleaved, and that each invocation will see the effects of all previous invocations. Once you've done that, you can and should remove the volatile modifier from nextSerialNumber. To bulletproof the method, use long instead of int, or throw an exception if nextSerialNumber is about to wrap.

  1. I understand that we can remove volatile after we make generateSerialNumber synchronized , as it is redundant. But, does it make any harm? Any performance penalty if I am having both synchronized and volatile like

private static volatile int nextSerialNumber = 0;
public static synchronized int generateSerialNumber() {
    return nextSerialNumber++;
}

  1. What does, use long instead of int means? I do not understand how this bulletproof the method?

It simply means that long will hold many more numbers than int.

or throw an exception if nextSerialNumber is about to wrap

implies that the concern here is that you run out of numbers and you end up with an overflow. You want to ensure that does not happen. The thing is, if you are at the maximum integer possible and you increment, the program does not fail. It happily goes not incrementing but the result is no longer correct.

Using long will postpone this possibility. Throwing the exception will indicate that it has happened.

What does, use long instead of int means?

It ensures that serial numbers don't roll over for a long, long time to come. Using an int you might use up all the available values (thus nextSerialNumber will have the maximum possible int value), then at the next increment the value is silently rolled over to the smallest (negative) int value, which is almost certainly not what you would expect from serial numbers :-)

IMHO volatile/AtomicInteger is faster than synchronized in a multi-threaded context. In a single threaded micro-benchmark they are much the same. Part of the reson for this is that synchronized is a OS call whereas volatile is entirely user space.

I get this output from the following program on Java 6 update 23.

Average time to synchronized++ 10000000 times. was 110368 us
Average time to synchronized on the class ++ 10000000 times. was 37140 us
Average time to volatile++ 10000000 times. was 19660 us

I cannot explain why synchronizing on the class is faster than a plain object.

Code:

static final Object o = new Object();
static int num = 0;

static final AtomicInteger num2 = new AtomicInteger();

public static void main(String... args) throws InterruptedException {
    final int runs = 10 * 1000 * 1000;
    perfTest(new Runnable() {
        public void run() {
            for (int i = 0; i < runs; i++)
                synchronized (o) {
                    num++;
                }
        }

        public String toString() {
            return "synchronized++ " + runs + " times.";
        }
    }, 4);
    perfTest(new Runnable() {
        public void run() {
            for (int i = 0; i < runs; i++)
                synchronized (Main.class) {
                    num++;
                }
        }

        public String toString() {
            return "synchronized on the class ++ " + runs + " times.";
        }
    }, 4);
    perfTest(new Runnable() {
        public void run() {
            for (int i = 0; i < runs; i++)
                num2.incrementAndGet();
        }

        public String toString() {
            return "volatile++ " + runs + " times.";
        }
    }, 4);
}

public static void perfTest(Runnable r, int times) throws InterruptedException {
    ExecutorService es = Executors.newFixedThreadPool(times);
    long start = System.nanoTime();
    for (int i = 0; i < times; i++)
        es.submit(r);
    es.shutdown();
    es.awaitTermination(1, TimeUnit.MINUTES);
    long time = System.nanoTime() - start;
    System.out.println("Average time to " + r + " was " + time / times / 10000 + " us");
}

One way of bulletproofing would be (in addition to the above)

if (nextSerialNumber >= Integer.MAX_VALUE) // throw an Exception;

or print out something, or catch that exception in calling routine

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM