Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AtomicInteger thread safety

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();
    }
}
like image 404
Anne Maier Avatar asked Nov 01 '25 21:11

Anne Maier


1 Answers

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() );
}
like image 72
Basil Bourque Avatar answered Nov 03 '25 12:11

Basil Bourque