How Much Faster is Multithreading in Java?


There can be many scenarios where we want our computer program to perform two tasks simultaneously. In Java, we can create separate Threads within a program for this purpose. A Java thread is a lightweight sub-process containing its own stack memory allocation. An advantage of running more than one process concurrently within a program rather than several entirely different processes is that it is easy to share memory between threads. But how much of a time saving advantage do we really get with multithreading? This post conducts an experiment to find out!

Experimental setup

Let's write a program whose only purpose is to add 100 Million random numbers stored as separate elements in a 1-dimensional array.


This is how we are going to initialize our test data, into array arr :


int[] arr  = new int[100000000];
Random rand = new Random();
		
for (int i = 0; i < arr.length; i++) {
     arr[i] = rand.nextInt(1000);
}	

First, let's run a timed test to measure how long it takes to find the sum of all 100 Million elements, and do it without any mulithreading:


Measurement #1 - Baseline program without Multithreading
import java.time.Duration;
import java.time.Instant;
import java.util.Random;

public class NoThreads{
	
	public static void main (String[] args) {
		
		// Initialize array with 100 million random values
		int[] arr  = new int[100000000];
		Random rand = new Random();
		
		for (int i = 0; i < arr.length; i++) {
			arr[i] = rand.nextInt(1000);
		}		
		
		// Record starting time
		Instant inst1 = Instant.now();
		
		// Perform addition operation
		long total = 0;
		for (int k : arr) {
			total += k;			
		}
		
		// Display Results
		System.out.println("Total Sum: " + total);		
		
		// Record ending time and Display total time elapsed
		Instant inst2 = Instant.now();
		Duration res = Duration.between(inst1, inst2);	
		long millis = res.toMillis();
		System.out.println(millis + " Milliseconds Elapsed");		
	}
}


Console Output:

Total Sum: 49947183384
34 Milliseconds Elapsed

Ok...so our time to beat is 34 milliseconds..can we do it?


Measurement #2 - The Multithreaded Threaded program

For our multithreaded approach we will create 2 threads, and each will concurrently traverse half of the array. In order to so this the sum operation will happen twice, but at the same time and each operation will only sum 50 Million (100 Million / 2) elements. Then at the end after both threads have computed their sums, their sums will be added to arrive at the final answer.


The real test here is whether the extra processing overhead of spawning and managing threads, as well as having to combine the intermediary results of sub-problems at the end are not so high as to make the multithreading approach inefficient compared to the baseline.


Also note the very limited use of System.out.println, since in my experimentation this command consumes several milliseconds and can make or break your potential time efficiency.


import java.time.Duration;
import java.time.Instant;
import java.util.Random;

class MyRunnable implements Runnable {
	
	private int start;
	private int end;
	static int[] arr;	
	private long total = 0;
	
	MyRunnable(int start, int end) {
		this.start = start;
		this.end = end;		
	}

	public long getTotal() {
		return this.total;
	}
	
	// This is called from THREAD.start();
	public void run() {		
		for (int k = this.start; k < this.end; k++) {
			this.total += arr[k];		
		}		
	}	
}

public class Solution {
	public static void main (String[] args) throws InterruptedException {
				
	// Initialize array with 100 million random values	
	MyRunnable.arr = new int[100000000];		
	Random rand = new Random();
			
	for (int i = 0; i < MyRunnable.arr.length; i++) {
		MyRunnable.arr[i] = rand.nextInt(10);			
	}
		
	// Record starting time
	Instant inst1 = Instant.now();
		
	// Create 2 Threads
	MyRunnable r1 = new MyRunnable(0, MyRunnable.arr.length / 2);
	MyRunnable r2 = new MyRunnable((MyRunnable.arr.length / 2) , MyRunnable.arr.length );
	Thread t1 = new Thread(r1, "Thread - 1");
	Thread t2 = new Thread(r2, "Thread - 2");
		
	// Start each thread and wait until both complete 
	t1.start();		
	t2.start();
	t1.join();
	t2.join();

	// Display Results
	System.out.println("Total Sum: " + (r1.getTotal() + r2.getTotal()));
	
	// Record ending time and Display total time elapsed
	Instant inst2 = Instant.now();		
	Duration res = Duration.between(inst1, inst2);			
	long millis = res.toMillis();
	System.out.println(millis + " Milliseconds Elapsed");
    }	
}


Console Output:

Total Sum: 449985655
23 Milliseconds Elapsed

That's 10 milliseconds faster!


Summary

Multithreading shaved off 10 milliseconds compared to the original program. This means that the same operation was completed in only 70% of the original time. Although this time difference is not a big deal for such a trivial program as demonstrated above, when applied to a much large and more repetitive context this savings can make a big difference. Also, consider the fact that we are not limited to only 2 threads in a real scenario and can spawn as many that are practical. However there is some memory and processing overhead of creating/maintaining threads in the first place, which has to be taken into consideration when designing your approach.