Parallel and Asynchronous Programming with Java 8

How Java 8 changes style of coding

package com.company;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> integerList = IntStream
                .range(1,100)
                .boxed()
                .collect(Collectors.toList());

        //imperative Style of coding
        int sum = 0;
        for (Integer integer:integerList){
                sum = sum+(integer*2);
        }
        System.out.println(sum);

        //Declarative Style of coding
        Integer sumValue =integerList
                .stream()
                .mapToInt(e->e*2).sum();

        System.out.println(sumValue);
    }
}

The Past

 

The Structure of concurrent code was very different from the structure of sequential code.

 

The Present 

 

The Structure of concurrent code is same as the structure of sequential code.

Making the previous code concurrent

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

    static Integer transform(Integer e){
        System.out.println("Current Thread :: "+Thread.currentThread());
        return e*2;
    }

    public static void main(String[] args) {

        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());
        
        Integer sumValue =integerList
                .parallelStream()
                .mapToInt(Main::transform).sum();


        System.out.println(sumValue);
    }
}

If you are source of the stream then use parallelStream If you are not the source of the stream then use parallel.

 

We can also make parallel streams on the basis of certain conditions

Convert a parallel stream to sequential Stream

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer transform(Integer e){
        System.out.println("Current Thread :: "+Thread.currentThread());
        return e*2;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());

        return integerList.parallelStream();
    }

    public static void main(String[] args) {

        Integer sumValue = getStream()
                .sequential()
                .mapToInt(Main::transform).sum();

        System.out.println(sumValue);
    }
}

Preserve Order in Map Operation

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer transform(Integer e){
        System.out.println("Current Thread :: "+Thread.currentThread());
        return e;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());

        return integerList.parallelStream();
    }

    public static void main(String[] args) {
         getStream()
                .mapToInt(Main::transform)
                .forEachOrdered(System.out::println);
    }
}

Preserve Order in Filter

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer transform(Integer e){
        System.out.println("Current Thread :: "+Thread.currentThread());
        return e;
    }

    static boolean filterElements(Integer e){
        return e%2==0;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());

        return integerList.parallelStream();
    }

    public static void main(String[] args) {
         getStream()
                .filter(Main::filterElements)
                .forEachOrdered(System.out::println);
    }
}

Reduce with Parallel Streams

We have to be really careful while working with reduce in parallel stream. The initial value of the stream should be identity.

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer reduceElements(int a,int b){
        return a+b;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());
        return integerList.stream();
    }

    public static void main(String[] args) {
        System.out.println(
        getStream()
                .parallel()
                .reduce(0,Main::reduceElements)
        );
    }
}

Following code will work fine with parallel stream because for addition 0 value is identity

Following code will not work correctly with parallel stream because for addition 12 value is not identity

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer reduceElements(int a,int b){
        return a+b;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());
        return integerList.stream();
    }

    public static void main(String[] args) {
        System.out.println(
        getStream()
                .parallel()
                .reduce(12,Main::reduceElements)
        );
    }
}

Order is preserved by default in findFirst

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static boolean filterElements(Integer e){
        return e%2==0;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,100)
                .boxed()
                .collect(Collectors.toList());
        return integerList.stream();
    }

    public static void main(String[] args) {
        System.out.println(
        getStream()
                .parallel()
                .filter(Main::filterElements)
                .findFirst()
        );
    }
}

Proof of efficiency of parallel stream

package com.company;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

    static Integer mapElements(Integer e)  {
        try {
            Thread.sleep(500);

        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("Current Thread>>"+Thread.currentThread());
        return e*2;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,10)
                .boxed()
                .collect(Collectors.toList());
        return integerList.stream();
    }

    public static void main(String[] args) {
        Long startTime= System.currentTimeMillis();
        System.out.println(
        getStream()
                .parallel()
                .map(Main::mapElements)
                .reduce(0,(a,b)-> a+b)
        );

        Long endTime= System.currentTimeMillis();

        System.out.println("Time taken "+(endTime-startTime));
    }
}

For computational Intensive

 

How many Threads should I create?

 

Number of threads you should create should be less then or equal to number of cores in CPU.

For IO Intensive

 

Number of threads <= number of cores / 1-blocking factor

 

0 < blocking factor < 1

Common Pool contains Pool of threads One less than total cores

Number of cores in your system

Runtime.getRuntime().availableProcessors();
 ForkJoinPool forkJoinPool= ForkJoinPool.commonPool();
 System.out.println(forkJoinPool);

Default Fork and Join Pool for Multi Threading

Configuring Threads Programatically

package com.company;

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

public class Main {

    static Integer mapElements(Integer e)  {
        try {
            Thread.sleep(50);

        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("Current Thread>>"+Thread.currentThread());
        return e*2;
    }

    static Stream<Integer> getStream(){
        List<Integer> integerList = IntStream
                .range(1,100)
                .boxed()
                .collect(Collectors.toList());
        return integerList.stream();
    }

    public static void main(String[] args) throws InterruptedException {


        Long startTime= System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool(8);
        forkJoinPool.submit(()->{
            System.out.println( getStream()
                    .map(Main::mapElements)
                    .reduce(0,(a,b)-> a+b));
        });
        forkJoinPool.shutdown();
        forkJoinPool.awaitTermination(30, TimeUnit.SECONDS);
        Long endTime= System.currentTimeMillis();
        System.out.println("Time taken "+(endTime-startTime));
    }
}

Lazy Evaluation in stream

It does not execute on a function on a collection of data.

It instead executes a collection on function on a piece of data

package com.company;

import java.util.Arrays;
import java.util.List;

public class Main {

   static List<Employee> getData(){
      return  Arrays.asList(
                new Employee("emp1",23,"mumbai"),
                new Employee("emp2",32,"delhi"),
                new Employee("emp3",35,"delhi"),
                new Employee("emp4",47,"mumbai"),
                new Employee("emp5",37,"delhi"),
                new Employee("emp6",52,"delhi")
                );
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(
                getData()
                        .stream()
                        .parallel()
                        .filter(e->{
                                    System.out.println(e.getName());
                            return  e.getAge()>33;
                        }
                            )
                        .filter(e->e.getCity().equals("delhi"))
                        .findFirst()
        );
    }
}
package com.company;

class Employee {

    private String name;
    private Integer age;
    private String city;

    public Employee(String name, Integer age, String city) {
        this.name = name;
        this.age = age;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", city='" + city + '\'' +
                '}';
    }
}

Exercise 1

  • Create a Stream of Integers and apply a map operation on it.
  • Now make the stream parallel and check its efficiency by using sleep.
  • Try to convert a parallel stream to sequential stream.
  • Preserve the Order in Map and Filter Operation.
  • Perform a reduce operation with and without identity value.
  • Configure Threads Programatically for a parallel operation.
  • Try Lazy evaluation with findFirst and try it with parallel

Old way of running Thread

new Thread(()-> System.out.println("Running Thread"))
                .start();
CompletableFuture.runAsync(()->{
            System.out.println("Running Thread");
        });

New Way of running thread

Running Thread with completion Callback

CompletableFuture
               .runAsync(()-> System.out.println("Running Thread"))
               .thenRun(()-> System.out.println("Complete Callback"));

Returning value from thread

CompletableFuture.supplyAsync(()->{
            System.out.println("Returning value from thread");
            return 2;
        });

Using the value returned from a thread

CompletableFuture.supplyAsync(()->{
            System.out.println("Returning value from thread");
            return 2;
        }).thenAccept(System.out::println);

Transforming the value from Thread

CompletableFuture.supplyAsync(()->{
            System.out.println("Returning value from thread");
            return 2;
        })
                .thenApply(e->e*2)
                .thenAccept(System.out::println);

Performing Async Operations

CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Returning value from thread");
            return 2;
        })
                .thenApplyAsync(e->{
                        try {
                        Thread.sleep(1000);
                        } catch (InterruptedException ex) {
                        ex.printStackTrace();
                        }
                      return  e*2;}
                )
                .thenAcceptAsync(System.out::println);

        Thread.sleep(3000);

Find out success and failure of callback

CompletableFuture.supplyAsync(()->{
            System.out.println("Returning value from thread");
            throw  new RuntimeException();
        }).whenCompleteAsync((a,b)->{
            if(b!=null){
                System.out.println("Exception");
                System.out.println(b.getMessage());
            }else{
                System.out.println("Success");
            }
        });
package com.company;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {

    static int performSomeOperation(){
        return 5;
    }

   static void delegatingCompletableFuture(CompletableFuture<Integer> completableFuture){

        completableFuture.complete(performSomeOperation());
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        CompletableFuture<Integer> completableFuture= new CompletableFuture<>();
        completableFuture
                .thenApplyAsync(e->e*2)
                .thenAcceptAsync(System.out::println);

        delegatingCompletableFuture(completableFuture);

        Thread.sleep(1000);

    }
}

Delegating CompletableFuture

Find out later in the program whether task is completed or cancelled

package com.company;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {

    static int performSomeOperation(){
        return 4;
    }

   static void delegatingCompletableFuture(CompletableFuture<Integer> completableFuture){

        int number = performSomeOperation();
        if(number%2==0) {
            completableFuture.complete(performSomeOperation());
        }else{
            completableFuture.cancel(true);
        }
       
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        CompletableFuture<Integer> completableFuture= new CompletableFuture<>();
        completableFuture
                .thenApplyAsync(e->e*2)
                .thenAcceptAsync(System.out::println);

        delegatingCompletableFuture(completableFuture);

        Thread.sleep(1000);

       if(completableFuture.isDone()){
           System.out.println("Task Completed");
       }

       if(completableFuture.isCancelled()){
           System.out.println("Task has been canceled");
       }
    }
}




Composing more than one Completable Futures together

CompletableFuture.supplyAsync(()-> 1)
                .thenCompose((e)-> CompletableFuture.supplyAsync(()->e+3 ))
                .thenCompose((e)-> CompletableFuture.supplyAsync(()->e+5 ))
                .thenAcceptAsync(System.out::println);

Combining Independent Completable Future

 CompletableFuture.supplyAsync(()->1).
                thenCombine(CompletableFuture.supplyAsync(()->2), (a,b)-> a+b ).
                thenAcceptAsync(System.out::println);
package com.company;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        boolean runException = true;

        CompletableFuture.supplyAsync(()->{
            if(runException) {
                throw new RuntimeException("Something went wrong");
            }
           return  1;
        })
                .exceptionally(th-> {
                    System.out.println(th);
                    // throw new RuntimeException("Something went wrong again");
                    return 2;
                })
                
                .thenApplyAsync(e->e*2)

                .exceptionally(th-> {
                    System.out.println(th);
                    throw new RuntimeException();
                })

                .thenAcceptAsync(System.out::println);

    }
}




Recovering from error

Exercise 2

  • Run a Thread using Completable Future
  • Run a callback after completion of the thread execution 
  • Return result on completion of a thread, transform the result using asyncApply and use the result using asyncAccept 
  • Find out the success and failure of a callback
  • Delegate the Completablefuture to some other method
  • After delegating future find out whether the task has been completed or not.
  • Try Composing and Combining twoCompletableFuture together
  • Throw an error in CompletableFuture and then try to recover from it.

Paralle and Asynchonous Programmin with Java 8

By Pulkit Pushkarna

Paralle and Asynchonous Programmin with Java 8

  • 992