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