I'm studying Java multi threading and trying to check performance with multiple threads.I am trying to check whether multi threading is better than with single thread. So, I wrote a code which sums to limit. It is working as I expected(multiple threads are faster than single thread) when limit gets larger but it didn't when limit is small like 100000L. Is this due to context-switching ? and is the code below is appropriate to check performance of multi threading ?
public class MultiThreadingSum {
long count = 0;
static long limit = 1000000000L;
static void compareMultipleThreadToSingleThread(int threadCnt) {
Runnable r = () -> {
MultiThreadingSum mts = new MultiThreadingSum();
long startTime = System.nanoTime();
while(++mts.count<=limit);
long endTime = System.nanoTime();
long estimatedTime = endTime - startTime;
double seconds = estimatedTime / 1000000000.0;
System.out.println(Thread.currentThread().getName()+", elapsed time : "+seconds);
};
for(int i=0; i<threadCnt; i++) {
new Thread(r, "multiThread"+i).start();
}
Runnable r2 = () -> {
MultiThreadingSum mts = new MultiThreadingSum();
long startTime = System.nanoTime();
while(++mts.count<=limit*threadCnt);
long endTime = System.nanoTime();
long estimatedTime = endTime - startTime;
double seconds = estimatedTime / 1000000000.0;
System.out.println(Thread.currentThread().getName()+", elapsed time : "+seconds);
};
new Thread(r2, "singleThread").start();
}
public static void main(String[] args) {
compareMultipleThreadToSingleThread(3);
}
}
Your code does not wait for the 3-thread experiment to finish before running the single-thread experiment. So you may be contaminating your results.
Your code seems needlessly complicated. Can't we run two separate experiments, one with 3 threads and one with 1 thread, separately, to reuse code?
In modern Java, we rarely need to address the Thread
class. Instead, use the executor service framework added to Java 5.
Putting this all together, perhaps your experiment should look more like the following.
Caveat: This is just a very rough cut, I've not thought it through, and my caffeination has been exhausted. So revise this code thoughtfully. Perhaps I can revise this code in a day or two.
package work.basil.threading;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Loopy
{
public static void main ( String[] args )
{
Loopy app = new Loopy();
List < Integer > inputThreadsLimit = List.of( 1 , 3 , ( Runtime.getRuntime().availableProcessors() - 1 ) );
for ( Integer numberOfThreads : inputThreadsLimit )
{
System.out.println("----------| Experiment for thread count: " + numberOfThreads + " |--------------------------");
Duration duration = app.demo( numberOfThreads ); // Waits here for the experiment to run to completion.
System.out.println( numberOfThreads + " = " + duration + " total, each: " + duration.dividedBy( numberOfThreads ) );
}
}
// Member fields
final private AtomicInteger count = new AtomicInteger( 0 );
private Duration demo ( final int numberOfThreads )
{
ExecutorService executorService = Executors.newFixedThreadPool( numberOfThreads );
long start = System.nanoTime();
for ( int i = 0 ; i < numberOfThreads ; i++ )
{
executorService.submit( new Task() );
}
executorService.shutdown(); // Ask the executor service to shutdown its backing pool of threads after all submitted tasks are done/canceled/failed.
try { executorService.awaitTermination( 1 , TimeUnit.HOURS ); } catch ( InterruptedException e ) { e.printStackTrace(); } // Tries to force the shutdown after timeout.
Duration elapsed = Duration.ofNanos( System.nanoTime() - start );
return elapsed;
}
class Task implements Runnable
{
@Override
public void run ( )
{
int countSoFar = count.incrementAndGet(); // Thread-safe way to access, increment, and write a counter.
// … add code here to do some kind of work …
System.out.println( "Thread ID " + Thread.currentThread().getId() + " is finishing run after incrementing the countSoFar to: " + countSoFar + " at " + Instant.now() );
}
}
}