-1

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);
    }
}
Mirza Asad
  • 606
  • 1
  • 7
  • 18
zz9z9
  • 11
  • 3
  • 1
    I think you are comparing time required to complete one task `r` with, time for `r*threadCnt`. You need to measure time from start of first `r` to time after last `r`. Use `join()` method or `CompletableFuture` class to wait for all threads to complete. – the Hutt Apr 01 '21 at 06:16
  • 1
    Do not use `System.out` statements in thread while measuring. For these reasons https://stackoverflow.com/a/18585337/15273968 – the Hutt Apr 01 '21 at 06:28
  • @onkarruikar I didn't even know that.. thanks a lot! – zz9z9 Apr 01 '21 at 07:16

2 Answers2

2

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() );
        }
    }
}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    Thanks a lot! but one question, isn't my code thread-safe ? because It declares new MultiThreadingSum() in arrow function. – zz9z9 Apr 01 '21 at 07:14
  • @zz9z9 Lambda syntax has nothing to do with thread-safety. However, that syntax is instantiating, so you are right, that count var is in separate instances, therefore thread-safe, I misread it as being static. I’ll correct my Answer tomorrow. – Basil Bourque Apr 01 '21 at 07:23
  • I thought each thread has its own MultiThreradingSum instance and count as an instance variable so each thread can't access other thread's MultiThreadingSum instance and count variable. Is it wrong ?? – zz9z9 Apr 01 '21 at 07:24
  • Nope, I was wrong, your are right about `count` being thread-safe. I deleted that criticism. – Basil Bourque Apr 01 '21 at 07:25
  • @BasilBourque in the `Task#run()` code, SOP statement will take most of the time, if we are doing just calculations. Is there any way to take it out of `run()` method? Like set onComplete listener on some kind of ExecutorService? – the Hutt Apr 01 '21 at 07:34
  • @onkarruikar Do you mean find a way to remove the println? I put it in because it does take some time to execute. I was trying to add some work so we can make a more useful test. And println calls cannot be optimized away by compiler. And println calls can be useful for understanding what is happening. – Basil Bourque Apr 01 '21 at 07:38
  • yes, I think we should remove println. If all thread use [same out stream](https://stackoverflow.com/a/18585337/15273968) it'll cause concurrency bottleneck. So I was wondering if there is out of the box solution in latest Java for adding thread completion listeners. – the Hutt Apr 01 '21 at 07:44
0

this is not a good example. the multi and single threaded solutions run simultaneously and on the same counter. so practically you run one multi threaded process with four threads. you need to run one solution until thread is complete and shutdown, then the other. the easiest solution would be to run the single threaded process as a simple loop in the main method and run the multi threaded solution after the loop completes. also, i would have two separate counters, or, you can assign zero to counter after single thread loop completes

Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47