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
- 980