facebook twitter youtube
by Amit Gupta - no comments

I code this program to explain Condition in java. Further I extended it to explain the use of

  1. Executor & ExecutorServices to make ThreadPool.
  2. Use of Future Object & Callable interface to enquiry/communicate with running thread.
  3. Use of Semaphore to control number of threads.
  4. Lock over synchronized block
  5. Threads inside threads
  6. Enum & EnumMap to store well known values.
  7. Condition (labeled wait & notify) to notify thread waiting for specific condition.

Classes


Cookie class: First I developed this code with this class then I omitted it. Because this class does nothing but getting/setting an EnumMap. So I directly moved this EnumMap into CookieMaker class

import java.util.EnumMap;

public class Cookie {
	EnumMap<Ingredient, Integer> ingredients;
	
	Cookie(){
		ingredients = new EnumMap<Ingredient, Integer>(Ingredient.class);
	}
	
	public void setIngredient(Ingredient i,int quantity){
		ingredients.put(i, quantity);
	}
	
	public EnumMap<Ingredient, Integer> getIngredients(){
		return ingredients;
	}
}

IngredientContainer Class – represents a container with some method to fill & consume container ingredient. As per the problem, a maker thread should wait if container is empty. If thread A waits for container 1 and B waits for container 2 and Filler fills container 1 then only thread A should be notified. So I have attached Condition object with container class itself.

code

/* 
 * 
 * (C) Copyright 2012-2013, by Amit Gupta (http://article-stack.com/).
 * 
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by 
 * the Free Software Foundation; either version 2.1 of the License, or 
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
 * USA. OR see <http://www.gnu.org/licenses/>. 
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
 * in the United States and other countries.]
 * 
 * version 1.0
 * 
 * ---------------------
 * IngredientContainer.java
 * ---------------------
 * 
 *
 */
public class IngredientContainer {
	private int capacity;//How much quantity of an ingredient a container can have
	private int quantityHeld;
	Condition empty;
	
	IngredientContainer(int c){
		capacity = c;
	}
	
	public int getQuantityHeld(){
		return quantityHeld;
	}
	
	public int getCapacity(){
		return capacity;
	}
	
	public int getQuantityRequired(){
		return capacity - quantityHeld;
	}
	
	public void fill(int n) throws Exception{
		if((n + quantityHeld) > capacity){
			throw new Exception("Overfilled");
		}
		quantityHeld += n; 
	}
	
	public boolean isEmpty(){
		return quantityHeld == 0;
	}
	/**
	 * 
	 * @param n filled units
	 * @return
	 */
	public int fillSafe(int n){
		int require = capacity  - quantityHeld;
		int toBeFilled = Math.min(require, n);
		quantityHeld +=toBeFilled ; 
		return toBeFilled ;
	}
	
	public void setEmptyCondition(Condition c){
		this.empty =c;
	}
	public boolean getIngredient(int n) throws Exception{
		if(n > capacity){
			throw new Exception("Accessing quantity more than capacity");
		}
		if(quantityHeld >= n){
			TimeUnit.SECONDS.sleep(1);
			quantityHeld -= n;
			return true;
		}
		
		System.out.println("Less Quantity Held");
		return false;
	}
}

Filler class – One filler is associated with one maker in our program who keeps watch on containers of associated maker only. And fills them.

code

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/* 
 * Filler checks a Container with regular interval. Either it fills Container
 * safely or check it before filling it. 
 * 
 * (C) Copyright 2012-2013, by Amit Gupta (http://article-stack.com/).
 * 
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by 
 * the Free Software Foundation; either version 2.1 of the License, or 
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
 * USA. OR see <http://www.gnu.org/licenses/>. 
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
 * in the United States and other countries.]
 * 
 * version 1.0
 * 
 * ---------------------
 * Filler.java
 * ---------------------
 * 
 *
 */
public class Filler {
	private ArrayList<IngredientContainer> ingredientContainers;
	int capacity = 6;
	private int checkInterval = 3;
	private int fillingQuantity = 2;
	private boolean isInterrupted = false;
	private Lock containerLock;
	
	public Filler(int c, Lock l) {
		ingredientContainers = new ArrayList<IngredientContainer>();
		capacity = c;
		containerLock = l;
	}
	
	public void addContainer(IngredientContainer c) throws Exception{
		if(ingredientContainers.size() == capacity)
			throw new Exception("Filler is overloaded");
		ingredientContainers.add(c);
	}
	/**
	 * Filler checks container with regular interval and fills if it is empty.
	 * @author Amit Gupta
	 */
	public void startFilling(){
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("Filler has started working");
				while(!isInterrupted){
					try {
						TimeUnit.SECONDS.sleep(checkInterval);
						System.out.println("Filler starts checking containers");
						containerLock.lock();
							Iterator<IngredientContainer> itr = ingredientContainers.iterator();
							while(itr.hasNext()){
								
								IngredientContainer ingredientContainer = itr.next();
								System.out.println("Require : "+ingredientContainer.getQuantityRequired());
								System.out.println("Capacity : "+ingredientContainer.getCapacity());
								System.out.println("Filling : "+ fillingQuantity);
								
									int filledQ = ingredientContainer.fillSafe(fillingQuantity);//Try to fill required quantity only.
									System.out.println("Filled " + filledQ );
									ingredientContainer.empty.signalAll();//This condition must be instantiate from CookieMaker
								
							}							
						containerLock.unlock();
					} catch (Exception e) {
						System.out.println(e.getMessage());
					}
				}
			}
		}).start();
	}
	
	public void stopFilling(){
		isInterrupted = true;
	}
	/**
	 * How long Filler should wait checking a container.
	 * @param checkInterval Seconds. default is 3 seconds.
	 */
	public void setCheckInterval(int checkInterval) {
		this.checkInterval = checkInterval;
	}
	
	/**
	 * How much quantity should be filled in a container, if it is empty.
	 * @param fillingQuantity default 10.
	 */
	public void setFillingQuantity(int fillingQuantity) {
		this.fillingQuantity = fillingQuantity;
	}
}

CookieMaker class – This is heart of the program. All other classes are created to support this class. It seems little bit complex. So we’ll understand it into parts.

code

package cookie;

import java.util.EnumMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/* 
 * CookieMaker bakes N cookies at a time. He can bake 1 type of cookie only  
 * Required containers must be installed in prior to bake a cookie.
 * 
 * (C) Copyright 2012-2013, by Amit Gupta (http://article-stack.com/).
 * 
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by 
 * the Free Software Foundation; either version 2.1 of the License, or 
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
 * USA. OR see <http://www.gnu.org/licenses/>. 
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
 * in the United States and other countries.]
 * 
 * version 1.0
 * 
 * ---------------------
 * CookieMaker.java
 * ---------------------
 * 
 * 
 */

public class CookieMaker implements Runnable{
	EnumMap<Ingredient,IngredientContainer> containers;
	int containerCapacity = 0;//How many containers a maker can have
	Semaphore bakingCapacity;//How many cookies a maker can bake
	EnumMap<Ingredient, Integer> cookie;
	private Lock bakingLock = new ReentrantLock();
	private Lock containerLock = new ReentrantLock();
	Filler fillingWorker = new Filler(6, containerLock);
	
	public void addContainer(Ingredient i,IngredientContainer c) throws Exception{
		//System.out.println("Adding " + i.toString() + " container.");
		if(containers.size() == containerCapacity){
			throw new Exception("Containers over loaded");
		}
		c.setEmptyCondition(containerLock.newCondition());
		//System.out.println("Condition is added to Container");
		fillingWorker.addContainer(c);
		//System.out.println("Container is added to Filler");
		containers.put(i,c);
		System.out.println(i.toString() + "container added");
	}	

	public void StartBaking(){
		try{
			System.out.println(bakingCapacity.getQueueLength() + " are waiting");
			bakingCapacity.acquire();
			System.out.println(bakingCapacity.availablePermits() + " more threads can enter");
			bakingLock.lock();
				System.out.println(Thread.currentThread().getName() + " :: Entered.");
				Set<Ingredient> ingredients = cookie.keySet();
				Iterator<Ingredient> itr = ingredients.iterator();
				ExecutorService pool;
				Set<Future<Boolean>> tasks = new HashSet<Future<Boolean>>();
				
				try{
					pool = Executors.newFixedThreadPool(containers.size());
					while(itr.hasNext()){
						System.out.println();
						final Ingredient ingredient = itr.next();
						tasks.add(pool.submit(new Callable<Boolean>() {
							@Override
							public Boolean call() throws Exception {
								return getIngredient(ingredient);
							}
						}));						
					}
					Iterator<Future<Boolean>> fuItr = tasks.iterator();
					boolean tasksCompleted = true;
					
					while(fuItr.hasNext()){
						Future<Boolean> f = fuItr.next();
						tasksCompleted &= f.get();
					}
					if(tasksCompleted){
						System.out.println("Mixture is ready. Backing cookie");
						pool.shutdown();
					}
				}catch(Exception e){
					e.printStackTrace();
				}
				
				System.out.println("Thread " + Thread.currentThread().getName() + " :: completed");	
			bakingLock.unlock();
			bakingCapacity.release();//let other threads start baking
			fillingWorker.stopFilling();
		}catch(InterruptedException ie){
			ie.printStackTrace();
		}
	}
	
	/**
	 * Take ingredients from a container. Wait if container is empty.
	 * @param ingredient
	 * @return
	 * @throws Exception 
	 * @throws InterruptedException 
	 */
	private boolean getIngredient(final Ingredient ingredient) throws InterruptedException, Exception{
		containerLock.lock();
			System.out.println("sub thread for " + ingredient.toString());
			IngredientContainer container = containers.get(ingredient);
			System.out.println(ingredient.toString() + " Container has " + container.getQuantityHeld());
			int quantity = cookie.get(ingredient);
			System.out.println("sub thread : Quantity require:" + quantity);
			while(!container.getIngredient(quantity)){
				container.empty.await();
			}
			//In real world I believe, this method will take some time to take ingredients from a container.
			TimeUnit.SECONDS.sleep(1);
			System.out.println("subThread " + ingredient.toString() + " has completed");
		containerLock.unlock();
		return true;
		
	}
	
	public static void main(String[] args) {
		IngredientContainer chocoPowderContainer = new IngredientContainer(12);
		IngredientContainer wheatPowderContainer = new IngredientContainer(15);
		
		CookieMaker cm = new CookieMaker(4,5);
		EnumMap<Ingredient, Integer> chocoWheatBar = new EnumMap<Ingredient, Integer>(Ingredient.class);
		
		chocoWheatBar.put(Ingredient.ChokoPowder, 3);
		chocoWheatBar.put(Ingredient.WheatPowder, 1);
		
		try{
			cm.addContainer(Ingredient.ChokoPowder, chocoPowderContainer);
			cm.addContainer(Ingredient.WheatPowder, wheatPowderContainer);
			cm.setCookie(chocoWheatBar);
		}catch(Exception e){
			System.out.println(e.getMessage());
		}

		new Thread(cm,"maker1").start();
		new Thread(cm,"maker2").start();
		new Thread(cm,"maker3").start();
		new Thread(cm,"maker4").start();
		new Thread(cm,"maker5").start();
		new Thread(cm,"maker6").start();
		
		cm.fillingWorker.startFilling();
		
	}
	
	@Override
	public void run() {
		if(checkSetup()){
			StartBaking();
		}else{
			System.out.println("Initial setup is required");
		}
	}
	
	public CookieMaker() {
		containers = new EnumMap<Ingredient,IngredientContainer>(Ingredient.class);
	}
	
	public CookieMaker(int bc) {
		bakingCapacity = new Semaphore(bc);
		containers = new EnumMap<Ingredient,IngredientContainer>(Ingredient.class);
	}
	
	public CookieMaker(int bc, int cc) {
		bakingCapacity = new Semaphore(bc);
		this.containerCapacity = cc;
		containers = new EnumMap<Ingredient,IngredientContainer>(Ingredient.class);
	}
	
	public void setCookie(EnumMap<Ingredient, Integer> cookie) {
		this.cookie = cookie;
	}

	public void setBakingCapacity(int bakingCapacity) {
		this.bakingCapacity = new Semaphore(bakingCapacity);
	}

	public void setContainerCapacity(int containerCapacity) {
		this.containerCapacity = containerCapacity;
	}
	
	/**
	 * Checks Maker's initial set up.
	 * @throws Exception
	 */
	public boolean checkSetup(){
		boolean signal = false;
		/*if(bakingCapacity. < 1){
			System.out.println("Maker can not bake cookies");
		}
		else*/ if(containerCapacity < 1){
			System.out.println("Container capacity is 0");
		}
		else if(containers.size() < 1){
			System.out.println("No container is installed.");
		}else{
			signal = true;
		}
		return signal;
	}	
}


I am not attaching output of above classes here since it is too long. I am chunking it and explaining you important parts.

bakingCapacity – Maker has the capacity of baking 4 cookies at a time. bakingCapacity semaphore keeps counting on running thread. If 4 threads are running and 1 completes its working, bakingCapacity allows 1 more thread. Review the output below;

0 are waiting
3 more threads can enter
maker1 :: Entered.
0 are waiting
2 more threads can enter
0 are waiting
1 more threads can enter
0 are waiting
0 more threads can enter
Thread maker1 :: completed
maker3 :: Entered.
0 more threads can enter
:

In above output, bakingCapacity allows 1st 4 threads to get entered. marker1 enters into its critical session. Total 4 threads are inside semaphore this time. Out of them 1 is running and other 3 are waiting. marker1 completes its work and get out from semaphore. marker3 enters into monitor. Now 2 threads are waiting and 1 is running. Total 3 threads are in semaphore. So bakingCapacity allows 1 more thread to get enter. And so on.


bakingLock: This lock ensures that only 1 thread can enter in its monitor. If you see in output you’ll notice following sequence;

maker1 :: Entered.
Thread maker1 :: completed
maker3 :: Entered.
Thread maker3 :: completed
maker5 :: Entered.
Thread maker5 :: completed
maker2 :: Entered.
Thread maker2 :: completed
maker4 :: Entered.
Thread maker4 :: completed
maker6 :: Entered.
Thread maker6 :: completed

Here the question arises if all threads are running in sequence then what is the need of multi-threading?

MultiThreadig is required when multiple tasks run parallel. And Locking is required when you fear that some other thread can change value of a variable required by current thread.

Let’s traverse each statement of StartBaking().

  1. What type of ingredients a cookie has?
  2. For each ingredient take required quantity from relevant container.
  3. If 1 container is empty then take ingredient from another container and for 1st container to get filled.
  4. Once ingredients are taken from all containers, bake cookie.

Ingredients are always fixed for a cookie type. It could be critical if a maker can produce multiple types of cookies. Moreover, local variables are always thread safe. So we need not to worry for point a).

For point b) & c), we will have to take ingredients from containers parallel. So for each container we’ll have to create one sub-thread. Containers are being filled by Filler. When a sub-thread takes ingredients from container, container’s quantity get reduced. So there is a critical condition when 2 threads check container quantity at the same time and both reduce it.

public boolean getIngredient(int n) throws Exception{
		:
		if(quantityHeld >= n){
			quantityHeld -= n;
			return true;
		}
		:
}

This section requires lock. Since we already acquiring lock in getIngredient() of Maker class. So there is no need of locking in StartBaking().

Once ingredients are taken, we don’t have to check any value which can be modified by any other thread.


containerLock – Condition discussed in above points proves the need of this lock. But we’ll analyze it.

java multithreading CookieMaker containers

Suppose marker1 creates 2 sub-threads A, B. marker2 creates C,D. All sub-threads call getIngredient() of Maker class. ChocoPowder Container has 2 units of powder while other container has 5 units. As per cookie recipe, A & C require 3 units while B & D require 1 unit.

  1. A : enter
  2. C: waiting
  3. A: finds insufficient quantity in container. waits and releases lock.
  4. C: enters
  5. Filler : fills 2 units. So total units in ChocoPowder container are 4.
  6. A & C both access getIngredient() of container class at the same time.
  7. A & C both finds sufficient quantity. So both reduce quantity and come out.
  8. Current quantity in ChocoPowder container is -2 units.

Where we require lock?

containerLock can be moved down since starting statements are creating local variables which are thread safe. Note that container is referencing field member. But its value is fixed so the next version of method could be as follows;

	private boolean getIngredient(final Ingredient ingredient) throws Exception{		
		IngredientContainer container = containers.get(ingredient);
		int quantity = cookie.get(ingredient);
		containerLock.lock();	
			while(!container.getIngredient(quantity)){
				container.empty.await();
			}
		containerLock.unlock();
		return true;		
	}

Even this move doesn’t solve the problem. Our need says that getIngredient() of container class should be accessed by only one thread at a time. So I must make it synchronized. I don’t require to synchronize fill() in our example.

This output will show that everything is fine after above changes.

output


A: sub thread for ChokoPowder
A: ChokoPowder Container has 0
A: sub thread : Quantity require:3
A: Less Quantity Held
B: sub thread for WheatPowder
B: WheatPowder Container has 0
B: sub thread : Quantity require:1
B: Less Quantity Held
C: sub thread for ChokoPowder
C: ChokoPowder Container has 0
C: sub thread : Quantity require:3
C: Less Quantity Held
D: sub thread for WheatPowder
D: WheatPowder Container has 0
D: sub thread : Quantity require:1
D: Less Quantity Held
Filler: Filler starts checking containers
Filler(choco): Require : 12
Filler(choco): Capacity : 12
Filler(choco): Filling : 2
Filler(choco): Filled 2
Filler(wheat): Require : 15
Filler(wheat): Capacity : 15
Filler(wheat): Filling : 2
Filler(wheat): Filled 2
A: Less Quantity Held
C: Less Quantity Held
B: subThread WheatPowder has completed
D: subThread WheatPowder has completed
Filler: Filler starts checking containers
Filler(choco): Require : 10
Filler(choco): Capacity : 12
Filler(choco): Filling : 2
Filler(choco): Filled 2
Filler(wheat): Require : 15
Filler(wheat): Capacity : 15
Filler(wheat): Filling : 2
Filler(wheat): Filled 2
A: subThread ChokoPowder has completed
C: Less Quantity Held

Mixture is ready. Backing cookie
Thread maker1 :: completed
Filler: Filler starts checking containers
Filler(choco): Require : 11
Filler(choco): Capacity : 12
Filler(choco): Filling : 2
Filler(choco): Filled 2
Filler(wheat): Require : 13
Filler(wheat): Capacity : 15
Filler(wheat): Filling : 2
Filler(wheat): Filled 2
subThread ChokoPowder has completed
Mixture is ready. Backing cookie
Thread maker2 :: completed


containerLock in Filler class : since we are creating only one thread of Filler class so there will not be another thread calling fillSafe() at the same time. Although writing it under lock is good practice. But some statements can be written out of lock as follows;

containerLock.lock();
	int filledQ = ingredientContainer.fillSafe(fillingQuantity);			ingredientContainer.empty.signalAll();
containerLock.unlock();

I hope this article can explain you more about multithreading. Keep reading …

Amit Gupta

Hey! this is Amit Gupta (amty). By profession, I am a Software Eng. And teaching is my passion. Sometimes I am a teacher, as you can see many technical tutorials on my site, sometimes I am a poet, And sometime just a friend of friends...

Leave a Reply

captcha