AtomicInteger thread safety

658 Views Asked by At

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();
    }
}
2

There are 2 best solutions below

0
On

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() );
}
4
On
    static AtomicInteger atomicInteger = new AtomicInteger(0); change to
    AtomicInteger atomicInteger = new AtomicInteger(1);// 0->1 no static
  
    while (atomicInteger.getAndIncrement() < 100) //change to:
    while (atomicInteger.getAndIncrement() != 101)//100->101

remove "static" for the variable atomicInteger. if you need to get exactly 1...100 (not 0...99) set the atomic value to 1 and in the loop set !=101 my print https://gyazo.com/6762c78ae44c0b06d05e316768114167

The comment clarified the question: the key point is the use of statics. use a semaphore

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

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

    private final Semaphore semaphore;

    public Print(final Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        int count = 0;
        try {
            semaphore.acquire();
            while ((count = atomicInteger.getAndIncrement()) != 101) {
            System.out.println(
            "COUNT::" + count + "::" + Thread.currentThread());
        }
           atomicInteger.set(1);
            
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            semaphore.release();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<>();
        Semaphore semaphore = new Semaphore(1);
        for (int i = 0; i < 5; i++)
            threads.add(new Thread(new Print(semaphore)));
        for (Thread thread : threads)
            thread.start();
        for (Thread thread : threads)
            thread.join();
    }
}