Java CountDownLatch

What is CountDownLatch?

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes, i.e., a kind of synchronization tool that allows one Thread  to wait for one or more Threads before it starts processing.

How does CountDownLatch work?

A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon — the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.

more information could be found at CountDownLatch in Oracle Document

When should CountDownLatch be used?

A CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.

A useful property of a CountDownLatch is that it doesn’t require that threads calling countDown wait for the count to reach zero before proceeding, it simply prevents any thread from proceeding past an await until all threads could pass.

Classical example of using CountDownLatch in Java  is any server side core Java application which uses services architecture, where multiple services is provided by multiple threads and the application can not start processing  until all services have finished their tasks successfully as shown in the CountDownLatch example here.

Example

Create a model class

package com.roytuts.java.thread.model;
public class Person {
	private String id;
	private String name;
	private String email;
	private String phone;
	private String city;
	private String state;
	private String country;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getState() {
		return state;
	}
	public void setState(String state) {
		this.state = state;
	}
	public String getCountry() {
		return country;
	}
	public void setCountry(String country) {
		this.country = country;
	}
}

Create a class which holds the list of Person object

package com.roytuts.java.thread.model;
import java.util.List;
public class Job {
	private List<Person> persons;
	public List<Person> getPersons() {
		return persons;
	}
	public void setPersons(List<Person> persons) {
		this.persons = persons;
	}
}

Create below three different service classes to fetch data from different sources for Person object.
Fetch data from file

package com.roytuts.java.thread.service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import com.roytuts.java.thread.model.Job;
import com.roytuts.java.thread.model.Person;
public class PersonFileService implements Callable<Job> {
	private CountDownLatch latch;
	public PersonFileService(CountDownLatch latch) {
		this.latch = latch;
	}
	@Override
	public Job call() throws Exception {
		// Dummy set of persons
		// the actual data should come from File
		Person p1 = new Person();
		p1.setId("1000");
		p1.setName("Debabrata");
		p1.setEmail("debabrata@gmail.com");
		p1.setPhone("1234567890");
		// set other fields for p1
		Person p2 = new Person();
		p2.setId("1000");
		p2.setName("Debina");
		p2.setEmail("debina@gmail.com");
		p2.setPhone("1234567890");
		// set other fields for p2
		Person p3 = new Person();
		p3.setId("1000");
		p3.setName("Baishali");
		p3.setEmail("baishali@gmail.com");
		p3.setPhone("1234567890");
		// set other fields for p3
		Person p4 = new Person();
		p4.setId("1000");
		p4.setName("Liton");
		p4.setEmail("liton@gmail.com");
		p4.setPhone("1234567890");
		// set other fields for p4
		List<Person> persons = new ArrayList<>();
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		persons.add(p4);
		Job job = new Job();
		job.setPersons(persons);
		// count down
		latch.countDown();
		return job;
	}
}

Fetch data from database

package com.roytuts.java.thread.service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import com.roytuts.java.thread.model.Job;
import com.roytuts.java.thread.model.Person;
public class PersonDBService implements Callable<Job> {
	private CountDownLatch latch;
	public PersonDBService(CountDownLatch latch) {
		this.latch = latch;
	}
	@Override
	public Job call() throws Exception {
		// Dummy set of persons
		// the actual data should come from Database
		Person p1 = new Person();
		p1.setId("1000");
		p1.setName("Debabrata");
		p1.setEmail("debabrata@gmail.com");
		p1.setPhone("1234567890");
		// set other fields for p1
		Person p2 = new Person();
		p2.setId("1000");
		p2.setName("Debina");
		p2.setEmail("debina@gmail.com");
		p2.setPhone("1234567890");
		// set other fields for p2
		Person p3 = new Person();
		p3.setId("1000");
		p3.setName("Baishali");
		p3.setEmail("baishali@gmail.com");
		p3.setPhone("1234567890");
		// set other fields for p3
		Person p4 = new Person();
		p4.setId("1000");
		p4.setName("Liton");
		p4.setEmail("liton@gmail.com");
		p4.setPhone("1234567890");
		// set other fields for p4
		List<Person> persons = new ArrayList<>();
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		persons.add(p4);
		Job job = new Job();
		job.setPersons(persons);
		// count down
		latch.countDown();
		return job;
	}
}

Fetch data from REST service

package com.roytuts.java.thread.service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import com.roytuts.java.thread.model.Job;
import com.roytuts.java.thread.model.Person;
public class PersonRestService implements Callable<Job> {
	private CountDownLatch latch;
	public PersonRestService(CountDownLatch latch) {
		this.latch = latch;
	}
	@Override
	public Job call() throws Exception {
		// Dummy set of persons
		// the actual data should come from REST webservice
		Person p1 = new Person();
		p1.setId("1000");
		p1.setName("Debabrata");
		p1.setEmail("debabrata@gmail.com");
		p1.setPhone("1234567890");
		// set other fields for p1
		Person p2 = new Person();
		p2.setId("1000");
		p2.setName("Debina");
		p2.setEmail("debina@gmail.com");
		p2.setPhone("1234567890");
		// set other fields for p2
		Person p3 = new Person();
		p3.setId("1000");
		p3.setName("Baishali");
		p3.setEmail("baishali@gmail.com");
		p3.setPhone("1234567890");
		// set other fields for p3
		Person p4 = new Person();
		p4.setId("1000");
		p4.setName("Liton");
		p4.setEmail("liton@gmail.com");
		p4.setPhone("1234567890");
		// set other fields for p4
		List<Person> persons = new ArrayList<>();
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		persons.add(p4);
		Job job = new Job();
		job.setPersons(persons);
		// count down
		latch.countDown();
		return job;
	}
}

Create below class to start different services on separate threads to fetch data and once all data are available then start the main thread

package com.roytuts.java.thread.latch.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.roytuts.java.thread.model.Job;
import com.roytuts.java.thread.model.Person;
import com.roytuts.java.thread.service.PersonDBService;
import com.roytuts.java.thread.service.PersonFileService;
import com.roytuts.java.thread.service.PersonRestService;
public class CountDownLatchMain {
	public static void main(String[] args) {
		int noOfThreads = 3;
		CountDownLatch latch = new CountDownLatch(noOfThreads);
		ExecutorService executorService = Executors.newFixedThreadPool(noOfThreads);
		PersonFileService fileService = new PersonFileService(latch);
		PersonDBService personDBService = new PersonDBService(latch);
		PersonRestService personRestService = new PersonRestService(latch);
		List<Future<Job>> futures = new ArrayList<>();
		System.out.println("get count : " + latch.getCount());
		futures.add(executorService.submit(fileService));
		futures.add(executorService.submit(personDBService));
		futures.add(executorService.submit(personRestService));
		try {
			latch.await();
			System.out.println("get count : " + latch.getCount());
			System.out.println("CountDownLatch Completed.");
			List<Person> aggregatedList = new ArrayList<>();
			for (Future<Job> future : futures) {
				List<Person> list = future.get().getPersons();
				aggregatedList.addAll(list);
			}
			executorService.shutdown();
			System.out.println("Aggregated List size : " + aggregatedList.size());
			// do something with aggregatedList
			// main tasks start here
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}
}

Run the above class, you will get below output

get count : 3
get count : 0
CountDownLatch Completed.
Aggregated List size : 12

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *