Home > front end >  Java For loop using int vs long
Java For loop using int vs long

Time:09-09

Context

I am reading a tech document which basically tells me the difference between using int vs long in fori loop:

public class SafePoint {

    public static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=()->{
            for (int i = 0; i < 1000000000; i  ) {
                num.getAndAdd(1);
            }
            System.out.println(Thread.currentThread().getName() "end!");
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("num = "   num);
    }

}

Expected Result:

Thread-1end!
Thread-0end!
num = 2000000000

public class SafePoint {

    public static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=()->{
            for (long i = 0; i < 1000000000; i  ) {
                num.getAndAdd(1);
            }
            System.out.println(Thread.currentThread().getName() "end!");
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("num = "   num);
    }

}

Result: num = 55406251 (or some random number less than 2000000000)
Thread-1end!
Thread-0end!

The most important concept he gives is the safepoint: We have safepoint in long fori loop due to uncounted loop but not int fori loop. Therefore in int fori loop, the sleep needs to wait for thread to finish and then do GC cause there is no safepoint when two threads are in int fori loop.

Issue:

I take his idea and try to reproduce them on my local machine then it failed: Basically no matter whether I use int or long, it is always the result similar to the second one. The num get printed first.

Then after carefully thinking, it can only be due to the JVM I use: java 11 corretto.

Question:

Can anyone test on java 8 and tell me whether that is the reason? Does java 11 change the way how we put Safe Points and why?

related links:

The tech doc trying to explain on and How to do GC in fori loop: Why Thread.sleep(0) can prevent gc in rocketmq?

CodePudding user response:

When you submit your runnable to any thread. You are submitted an independent task.

 Runnable runnable=()->{
            for (long i = 0; i < 1000000000; i  ) {
                num.getAndAdd(1);
            }
            System.out.println(Thread.currentThread().getName() "end!");
        };

afterthen it is vendor dependent to decide which thread to start first. Though you specified

t1.start();
t2.start();

It doesn't mean that t1 will output first and then t2 will output. It is totally dependent on the scheduler.

So,

Expected Result:

Thread-1end!
Thread-0end!
num = 2000000000

is incorrect.

CodePudding user response:

ANSWER: It's because JDK8 defaults to the parallel GC, and JDK9 to the G1 GC, and this explains all.

Proof

On an arm-chip mac, with Eclipse Temurin OpenJDK, both version 1.8 and 1.17, you get the following behaviour:

1.17 1.8
int ~50m 2000m*
long ~50m ~50m

*) This answer appears half a minute later, the explanation isn't that the 2 threads just get through the billion loops within the second timespan.

In other words, exactly as you describe. Given that this is temurin, it's not 'amazon coretto' specifically that's at fault.

However, if I then run the int variant on 1.17, but with:

/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/bin/java -XX: UseParallelGC SafePoint

It prints 2000m just like temurin-8 does (and after ~half a minute of chugging away at it).

This therefore fully explains the difference:

  • If you run this code with int and using the Parallel GC, you get 2000m (eventually; it takes a while, the threads finish before the print code happens).
  • Otherwise (you use the G1 GC, or you use long), you get ~50m; the 'print the number' code runs well before the threads finish.

If you don't explicitly choose which garbage collection impl you want, up to JDK8 you default to the parallel GC, starting with JDK9, you get the G1 collector (and perhaps on recent versions zgc or whatnot. Not the parallel collector, in any case).

It's therefore clearly not about how the JVM injects safepoints, and you can inspect this by asking the JVM to print you the machine code it is generating for you (many blogposts you can search the web for explain precisely how to do this and what to look for) - you'll find JDK17s do similar things as JDK8 in this regard.

No, instead, the Parallel GC blocks on hitting a safepoint in every thread, whereas the G1 collector does not, a much simpler explanation.

There are many reasons why the default GC impl has been changed away from the parallel collector, and this is presumably one of them.

  • Related