Q:

Demonstrating the speed advantage of parallel computation

I am teaching a class on parallel computation and am looking for a simple example that illustrate the speed gains from using "CompletableFuture"s in Java. Currently my example problem is very simple: calculate the sum of an array. Each CompletableFuture calculates the sum of about 1/4 of the array, and when all of them finish, the main program calculates the total sum.
The line sum += Math.pow(Math.pow(Math.pow(array[i],0.3),0.4),0.5); was initially just sum += array[i]; I added all the "pow"s just to make the serial computation appear longer.
Currently, on my machine with 4 cores, when I put NUM_OF_THREADS = 1 the sum is calculated in 4 seconds, while with NUM_OF_THREADS = 4 the sum is calculated in 5.5 seconds, so this is not a good example - it does not show that parallel computation is more efficient.
what should I change in the example, to demonstrate the advantage of parallel computation?
public class ConcurrencyTest {

    static final int SIZE = 10000000;
    static final int NUM_OF_THREADS = 1;

    static int iThread;
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int[] array = new int[NUM_OF_THREADS*SIZE];
        Arrays.fill(array, 1);

        ExecutorService executor = Executors.newCachedThreadPool();
        Instant startTime = Instant.now();

        CompletableFuture<Integer>[] futures = new CompletableFuture[NUM_OF_THREADS];
        for (iThread=0; iThread<NUM_OF_THREADS; ++iThread) {
            final int iThreadLocal = iThread;
            futures[iThread] = CompletableFuture.supplyAsync( () -> {
                int sum=0;
                int start = iThreadLocal*SIZE;
                int end = (iThreadLocal+1)*SIZE;
                for (int i=start; i<end; ++i) {
                    Thread.yield();
                    sum += Math.pow(Math.pow(Math.pow(array[i],0.3),0.4),0.5);
                }
                System.out.println(iThreadLocal+": "+sum + 
                        "   "+Duration.between(startTime, Instant.now()).toMillis()+" [ms]");
                return sum;
            }, executor);
        }

        CompletableFuture.allOf(futures).thenRun(
                () -> {
                    try {
                        Arrays.stream(new int[] {1,2,3}).sum();
                        int totalSum = 0;
                        for (iThread=0; iThread<NUM_OF_THREADS; ++iThread)
                            totalSum += futures[iThread].get();
                        System.out.println("Total: "+totalSum+ 
                                "   "+Duration.between(startTime, Instant.now()).toMillis()+" [ms]");
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
        );

        executor.shutdown();
    }
}

A:

Currently each future processes SIZE elements (increasing the NUM_OF_THREADS increases the total workload (NUM_OF_THREADS * SIZE)). Instead you want to divide the workload between each of the futures:
int start = SIZE / NUM_OF_THREADS * iThreadLocal;
int end = SIZE / NUM_OF_THREADS * (iThreadLocal + 1);

Besides that iThread could (and should) be a local variable. The spacing between operators is inconsistent, you should add whitespace around the operators (except for unary operators) to improve readability.

