Demonstrate Executor Service

Overview
We encounter implementations where one main method calls multiple sub-methods (internal/external to current program) in a sequence and wait for the completion of all the methods summing up the total execution time equal to sum of execution times of individual methods. Assuming all the sub-methods are independent and doesn’t need any hierarchy of execution, java concurrency API can used to process all the sub-method calls in parallel there by reducing the execution time nearly equivalent to just longest sub method execution time.

Possible processing options:

  • Processing via for loop.
  • Processing using Executor service.

Technical overview:

  • ExecutorService:
    • ExecutorService is an interface which exposes methods, which is capable of processing the tasks asynchronously. ExecutorService is represents ThreadPool implementation and which is actually an abstraction of ThreadPool.
    • The submit method is used to submit Callable and Runnable tasks for asynchronous process.
    • The shutdown need to be called after processing, in order to reclaim all the resources.
    • Refer ExecutorService javadocs for details.
  • Executors:
  • Callable:
  • Futures:
    • The result of asynchronous computation is represented by Futures.
    • The get method is used to get the result of futures which is synchronous in nature.
    • Java 8 has CompletableFuture which supports asynchronous callback operation.
    • Refer CompletableFuture Javadocs for details.

Source Code Snippets

The code snippet below has below implementations.

  • Creation of Custom Callable.
  • Create a method which mimics external call and wait for 2 secs
  • Call the external call method via conventional java for loop 4 times and check the time taken.
  • Create a thread pool of four threads via ExecutorService and execute external call method in parallel to check the completion time.

 

package com.siva.mythoughts.executorservice;

public class DemonstrateExecutorService {

public static void main(String[] args) {

DemonstrateExecutorService demonstrateExecutorService = new DemonstrateExecutorService();

long startConcurrProcess = System.currentTimeMillis();

// Creates thread pool with four threads
// You can change the number of tests and re-run the program to notice in difference of execution time
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>();

for (int i = 0; i < 4; i++) {
DemonstrateExecutorService.CustomCallable callable = demonstrateExecutorService.new CustomCallable(i);
// ExecutorService submit method accepts instance of Callable/Runnable. Use Callable when needs some output
// ExecutorService submit method returns Future instance
futures.add(executorService.submit(callable));
}

// if its not shutdown, hanging threads may effect shutdown of jvm
executorService.shutdown();
while (executorService.isTerminated()) {
// wait till the process is completed
}
for (Future<Integer> future : futures) {
:::
future.get();
:::
}

// Excecution via standard for loop
for (int i = 0; i < 4; i++) {
demonstrateExecutorService.mimicExternalSystemCall(i);
}
}

private class CustomCallable implements Callable<Integer> {

public Integer call() {
return mimicExternalSystemCall(input);
}
}

private int mimicExternalSystemCall(int i) {
::::
Thread.sleep(2000);
::::
}
}

Unique identification of entities in Collections

Overview
Every java class need to have equals() and hashcode() methods implemented which are used for distinctly identify the object. The equals() method is used by methods like contains(Object o), remove(Object o) and put() to uniquely identify the objects.

Business use cases:

  • When a specific business key need to be used for any of the methods specified above, equals() and hashcode() need to be overwritten.
  • A person entity need to be identified uniquely based on combination of name and dob, which combination need to be verified before adding to the list.
  • A map of persons need to be maintained where the key represent the Person entity which can be uniquely identified based on combination of name and dob.

Source Code snippets:

  • Implementation of hashcode() and equals():
    • The below source code snippet provides implementation of equals and hashcode.
    • The equals method checks the combination of name and dob as unique key.
    • As per java contract every object with distinct equals result need to have distinct hashcode which makes it mandatory to override hashcode also every time equals is overwritten.
	private class Person {

		private String name;
		private Date dob;
		private String address;
		private String skillset;

		public Person(String name, Date dob) {
			this.name = name;
			this.dob = dob;
		}
:::::::::::::::::::::::::::::::::::
		@Override
		public int hashCode() {
			int dateInt = (int) (dob.getTime() / 1000);
			int nameHash = name.hashCode() + dateInt + 31;
			return nameHash;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == null) return false;
			if (!(obj instanceof Person)) return false;
			if (obj == this) return true; 
			Person person = (Person) obj;
			if (person.name.equalsIgnoreCase(name) && person.dob.equals(dob)) {
				return true;
			} else {
				return false;
			}
		}
	}

 

  • Testing contains method on List:
    • Create person1 and person2 entities with same name(Tom) and dob(1980-01-01).
    • Check whether entity exist in list using contains method.
    • The first sysout results in personlist size as 1, as contains method returns false as person2 entity business key matches to that of person1 entity.
    • Modifying the person2 dob will allow it to be added to the arraylist
    • Comment the equals and hashcode method in Person class and re-try the examples to notice how contains method works.
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
		List<Person> personsList = new ArrayList<DemoHasCodeEqualsImpl.Person>();
:::::::::::::::::::::::::::::::::::::::::::::::::::::::
	Person person1 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-01-01"), "US", "Run");
	personsList.add(person1);
	Person person2 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-01-01"), "Germany", "Rob");
	if (!personsList.contains(person2)) {
		personsList.add(person2);	
	}
	System.out.println("Person list when name and dob are same for person2 "+personsList.size());
	person2 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-02-01"), "Germany", "Rob");
	if (!personsList.contains(person2)) {
			personsList.add(person2);	
	}
	System.out.println("Person list when name and dob are different for person2 "+personsList.size());

  • Testing unique key in Map:
    • Create person1 and person2 entities with same name and dob
    • Add both the persons to map and check the size of the map
    • Its noticed there is only entry in the map and its also noticed person2 data overwrites the data of person1
    • Now modify person2 dob and re-try it, to notice both the entities are added to the map
    • Comment the equals and hashcode method in Person class and re-try the examples to notice how put method works.
	Person person1 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-01-01"), "US", "Run");
	personMap.put(person1, person1);
	Person person2 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-01-01"), "Germany", "Rob");
	personMap.put(person2, person2);
	System.out.println("PersonMap expects to have only one entity:--> "+personMap.size());
	person2 = codeEqualsImpl.createPerson("Tom", dateFormat.parse("1980-02-01"), "Germany", "Rob");
	personMap.put(person2, person2);
	System.out.println("PersonMap expects to have two entities:--> "+personMap.size());