Thoughts of a software developer

09.09.2023 18:46 | Modified 10.09. 21:04
A simple way to run methods parallelly in Java

Many times there a need to run multiple methods, combine the data and return the result. Normally this is done by first getting method1 and after we have the result, then get the method2 result and combine them. If the methods take time to complete, we can execute the methods parallelly to get the results faster.

Next example shows getting three methods which return a different type of result and combine and print the results.

Using futures

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class HelloWorld {
    public void execute() throws Exception {
        Date date = new Date();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Future<String> future1 = executorService.submit(this::method1);
        Future<Test> future2 = executorService.submit(this::method2);
        Future<Test> future3 = executorService.submit(this::method3);
        String result1 = future1.get();
        Test result2 = future2.get();
        Test result3 = future3.get();
    
        System.out.println("result = " + result1 + ", " + result2 + ", " + result3);
        System.out.println("run took " + (
            TimeUnit.MILLISECONDS.toSeconds(new Date().getTime()) - 
            TimeUnit.MILLISECONDS.toSeconds(date.getTime()))%60 + " seconds");
    
        executorService.shutdown();
    }
    
    private String method1() throws Exception {
        System.out.println("method1");
        TimeUnit.SECONDS.sleep(3);
        return "method1";
    }
    
    private Test method2() throws Exception {
        System.out.println("method2");
        TimeUnit.SECONDS.sleep(3);
        return new Test("1", "some text");
    }
     private Test method3() throws Exception {
        System.out.println("method3");
        TimeUnit.SECONDS.sleep(3);
        return new Test("1", "some text");
    }

     public static void main(String []args) {
         try {
            new HelloWorld().execute();
         } catch (Exception e) {
             System.out.println(e);
         }
     }
     
     private class Test {
        private String id;
        private String text;
        public Test(String id, String text) {
            this.id = id;
            this.text = text;
        }
        
        public String toString() {
            return "{ id: " + id + ", text: " + text + " }";
        }
    }
}

This will print:

method1
method2
method3
result = method1, { id: 1, text: some text }, { id: 2, text: some text }
run took 3 seconds

Each of the methods had 3 second timeout and they all were executed in the same time.

Using parallel Stream

The same can be achieved using parallel stream.

import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

public class HelloWorld {
    public void execute() throws Exception {
        Date date = new Date();
        ForkJoinPool pool = new ForkJoinPool(2);
        List<Object> res = pool.submit(() -> Stream.of(method1(), method2())
            .parallel()
            .collect(Collectors.toList())
        ).get();
    
        System.out.println("result = " + res.get(0) + ", " + res.get(1));
        System.out.println("run took " + (
            TimeUnit.MILLISECONDS.toSeconds(new Date().getTime()) - 
            TimeUnit.MILLISECONDS.toSeconds(date.getTime()))%60 + " seconds");
    }
    
    private String method1() throws Exception {
        System.out.println("method1");
        TimeUnit.SECONDS.sleep(3);
        return "method1";
    }
    
    private Test method2() throws Exception {
        System.out.println("method2");
        TimeUnit.SECONDS.sleep(3);
        return new Test("1", "some text");
    }
     public static void main(String []args) {
         try {
            new HelloWorld().execute();
         } catch (Exception e) {
             System.out.println(e);
         }
     }
     private class Test {
        private String id;
        private String text;
        public Test(String id, String text) {
            this.id = id;
            this.text = text;
        }
        
        public String toString() {
            return "{ id: " + id + ", text: " + text + " }";
        }
    }
}

outputs

method1
method2
result = method1, { id: 1, text: some text }
run took 6 seconds

As you can see, parallel stream execution is still taking double the time.

Async

import java.util.stream.Collectors;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
import java.util.Date;

public class HelloWorld {
    public void execute() throws Exception {
        Date date = new Date();
        List<Object> tasks = List.of(
            CompletableFuture.supplyAsync(() -> method1()), 
            CompletableFuture.supplyAsync(() -> method2()),
            CompletableFuture.supplyAsync(() -> method3()),
            CompletableFuture.supplyAsync(() -> method4()),
            CompletableFuture.supplyAsync(() -> method5())
        )
        .parallelStream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList());
        
        for(Object task : tasks) {
            System.out.println(task);
        }
        System.out.println("run took " + (
            TimeUnit.MILLISECONDS.toSeconds(new Date().getTime()) - 
            TimeUnit.MILLISECONDS.toSeconds(date.getTime()))%60 + " seconds");
    }
    private String method1() {
        System.out.println("method1");
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("slept for a second");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return "method1";
    }
    
    private Test method2() {
        System.out.println("method2");
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("slept for a second");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return new Test("1", "some text");
    }
    private Test method3() {
        System.out.println("method3");
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("slept for a second");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return new Test("2", "some text");
    }
    private Test method4() {
        System.out.println("method4");
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("slept for a second");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return new Test("3", "some text");
    }
    private Test method5() {
        System.out.println("method5");
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("slept for a second");
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return new Test("4", "some text");
    }
     public static void main(String []args) {
         try {
            new HelloWorld().execute();
         } catch (Exception e) {
             System.out.println(e);
         }
     }
     private class Test {
        private String id;
        private String text;
        public Test(String id, String text) {
            this.id = id;
            this.text = text;
        }
        
        public String toString() {
            return "{ id: " + id + ", text: " + text + " }";
        }
    }
}

And the output


method1
method2
method3
method4
method5
slept for a second
slept for a second
slept for a second
slept for a second
slept for a second
method1
{ id: 1, text: some text }
{ id: 2, text: some text }
{ id: 3, text: some text }
{ id: 4, text: some text }
run took 1 seconds

#Java #concurrent #parallel #stream