3

How can I achieve that the while loop here is always executed exactly 100 times. When I execute the code, in rare cases it prints 99 or 98 lines on the console and not always 100, which I do not understand.

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class Print implements Runnable {
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public void run() {
        while (atomicInteger.getAndIncrement() < 100)
            System.out.println(Thread.currentThread());
    }

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Thread> threads = new ArrayList<>();

        for (int i = 0; i < 5; i++)
            threads.add(new Thread(new Print()));

        for (Thread thread : threads)
            thread.start();

        for (Thread thread : threads)
            thread.join();
    }
}
Anne Maier
  • 189
  • 6
  • 4
    This code should work because `getAndIncrement` is thread-safe, are you sure it prints only 98 or 99 times? – Karol Dowbecki Jun 08 '21 at 10:50
  • Thanks Karol. Could the IDE or other programs running in the background be affecting the output? I tried to reproduce the problem but was unable to. I had copied the console output in Excel and I am sure that it was once 98 and the other 99 lines. – Anne Maier Jun 08 '21 at 12:02
  • I couldn't reproduce your problem and the code looks correct, I don't know how you could have received 98 or 99 instead of 100. – Karol Dowbecki Jun 08 '21 at 12:03
  • 3
    The counting part looks ok to me. I have a tiny smidgen of doubt about the parallel println calls; the [description](https://docs.oracle.com/javase/7/docs/api/java/io/PrintStream.html#println(java.lang.Object)) suggests that printing the value and printing the line terminator are distinct actions, which I suppose *may* result in two values followed by two newlines - but then you'd have obviously-wrong output. – iggy Jun 08 '21 at 13:06

1 Answers1

2

Unable to replicate your reported experience.

In my mind’s debugger, that seems correct as I cannot see any thread-safety fault in your code.

To automate further testing, I altered your code as follows. Rather than call System.out.println, I added the thread ID to a List of Long. I made the list a CopyOnWriteArrayList for thread-safety. I can programmatically detect if the resulting list is not exactly 100 elements in size.

package work.basil.demo;

import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class Print implements Runnable
{
    static AtomicInteger atomicInteger = new AtomicInteger( 0 );
    static CopyOnWriteArrayList < Long > list = new CopyOnWriteArrayList <>();

    @Override
    public void run ( )
    {
        while ( atomicInteger.getAndIncrement() < 100 )
            //System.out.println(Thread.currentThread());
            list.add( Thread.currentThread().getId() );
    }

    public static void main ( String[] args ) throws InterruptedException
    {
        System.out.println( "INFO - demo starting. " + Instant.now() );
        for ( int cycle = 0 ; cycle < 1_000_000 ; cycle++ )
        {
            ArrayList < Thread > threads = new ArrayList <>();

            for ( int i = 0 ; i < 5 ; i++ )
                threads.add( new Thread( new Print() ) );

            for ( Thread thread : threads )
                thread.start();

            for ( Thread thread : threads )
                thread.join();

//            System.out.println( "list.size() = " + list.size() );
//            if ( list.size() == 100 ) { System.out.println( "DEBUG list.size() = " + ( list.size() ) ); }
            if ( list.size() != 100 ) { System.out.println( "DEBUG list.size() = " + ( list.size() ) ); }
        }
        System.out.println( "INFO - demo done. " + Instant.now() );
    }
}

When run on a Mac mini (2018) 3 GHz Intel Core i5 with six real cores and no hyper-threading, and 32 GB 2667 MHz DDR4, using Java 16 within IntelliJ. Running cycle to a million takes about 5 minutes.

INFO - demo starting. 2021-06-08T22:11:56.010181Z
INFO - demo done. 2021-06-08T22:16:26.982616Z

ExecutorService

By the way, in modern Java we rarely need to address the Thread class directly. Instead, use the Executors framework added to Java 5.

Here is revised version of the code above, rejiggered to use an executor service.

public static void main ( String[] args ) throws InterruptedException
{
    System.out.println( "INFO - demo starting. " + Instant.now() );
    for ( int cycle = 0 ; cycle < 1_000_000 ; cycle++ )
    {
        ExecutorService executorService = Executors.newFixedThreadPool( 5 );

        int countTasks = 5;
        for ( int i = 0 ; i < countTasks ; i++ )
        {
            executorService.submit( new Print2() );
        }

        executorService.shutdown();
        executorService.awaitTermination( Duration.ofMinutes( 7 ).toSeconds() , TimeUnit.SECONDS );

//            System.out.println( "list.size() = " + list.size() );
//            if ( list.size() == 100 ) { System.out.println( "DEBUG list.size() = " + ( list.size() ) ); }
        if ( list.size() != 100 ) { System.out.println( "DEBUG list.size() = " + ( list.size() ) ); }
    }
    System.out.println( "INFO - demo done. " + Instant.now() );
}
Basil Bourque
  • 262,936
  • 84
  • 758
  • 1,028