CountDownLatch in Java With Examples

CountDownLatch in Java is a synchronization aid that allows one or more threads to wait for a set of operations being performed in other threads to complete.

CountDownLatch class was added in Java 1.5 and it is part of java.util.concurrent package in Java.

How CountDownLatch works in Java

A CountDownLatch is initialized with a given count then there are two methods await() and countDown() that block and release the threads.

  1. await()- Causes the current thread to wait until the latch has counted down to zero.
  2. countDown()- Decrements the count of the latch, releasing all waiting threads if the count reaches zero.

CountDownLatch as the name itself goes can be thought of as fastening the latch so that the current thread is blocked when await method is called and waits for the countdown to reach zero from the given count. When the count reaches zero latch is opened for the thread(s) to proceed.

Java CountDownLatch constructor

CountDownLatch(int count)- Constructs a CountDownLatch initialized with the given count. Here count specifies the number of events that must happen to make the count zero by counting down after each event.

Java CountDownLatch methods

Apart from await() and countDown() method which are already showed above following are some of the other methods in the CountDownLatch class.

  • await(long timeout, TimeUnit unit)- Causes the current thread to wait until the latch has counted down to zero, unless the thread is interrupted, or the specified waiting time elapses.
  • getCount()- Returns the current count.

CountDownLatch Java example

Let’s say you have a scenario where you have a set of threads performing some task but you want those thread to start only after some set up work is finished by the main thread. Once the set of threads start, main thread should wait and continue only after set of threads have finished the task. In this case you can use two countdown latches-

First CountDownLatch is used to prevent any thread from starting its operation until the main thread is finished with set up tasks.

Second CountDownLatch acts as a completion signal that makes the set of threads to wait until all threads have completed. That gives main thread a signal to proceed.

import java.util.concurrent.CountDownLatch;

public class CLDemo {
  public static void main(String[] args) {
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch doneLatch = new CountDownLatch(3);
    for(int i = 0; i < 3; i++) {
      new Thread(new Worker(startLatch, doneLatch)).start();;
    }
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("Main thread - Doing setup");
    //Signal from main thread for other threads to start
    startLatch.countDown();
    try {
      // Main thread waiting for other threads to finish
      doneLatch.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("Main thread - Proceed after all the threads are done");
  }
}

class Worker implements Runnable{
  private final CountDownLatch startLatch;
  private final CountDownLatch doneLatch;
  Worker(CountDownLatch startLatch, CountDownLatch doneLatch) {
    this.startLatch = startLatch;
    this.doneLatch = doneLatch;
  }
  @Override
  public void run() {
    try {
      System.out.println("Calling await ...");
      // don't start until set up is done
      startLatch.await();
      System.out.println("Doing processing " + Thread.currentThread().getName());
      doneLatch.countDown();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Calling await ...
Calling await ...
Calling await ...
Main thread - Doing setup
Doing processing Thread-0
Doing processing Thread-2
Doing processing Thread-1
Main thread - Proceed after all the threads are done

As you can see from the output three threads are started and all three start executing run method also but blocked by the call to startLatch.await(); This ensures that the threads don’t start their processing until the main thread is done with the set up. Once the startLatch.countDown(); is called in the main thread, threads start the processing.

Now the main thread waits because of the call to doneLatch.await(); Since the doneLatch is initialized with the count of 3 so the main thread is blocked until the countDown() method is called 3 times. Once the count reaches 0 main thread proceeds with its execution.

CountDownLatch in Java is not reusable

One important point about CountDownLatch in Java is that it is not reusable like the other synchronization aid CyclicBarrier which can be reused. Once the count reaches zero it can’t be reset. Once the countdown reaches zero, call to await() method won't block any thread so ensure the synchronization between the count CountDownLatch is initialized with and how many times countDown() method is called.

In the previous example if we create 6 threads instead of 3 but let the count in doneLatch remain 3.

public class CLDemo {
  public static void main(String[] args) {
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch doneLatch = new CountDownLatch(3);
    for(int i = 0; i < 6; i++) {
      new Thread(new Worker(startLatch, doneLatch)).start();;
    }
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("Main thread - Doing setup");
    //Signal from main thread for other threads to start
    startLatch.countDown();
    try {
      // Main thread waiting for other threads to finish
      doneLatch.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("Main thread - Proceed after all the threads are done");
  }
}

class Worker implements Runnable{
  private final CountDownLatch startLatch;
  private final CountDownLatch doneLatch;
  Worker(CountDownLatch startLatch, CountDownLatch doneLatch) {
    this.startLatch = startLatch;
    this.doneLatch = doneLatch;
  }
  @Override
  public void run() {
    try {
      System.out.println("Calling await ...");
      // don't start until set up is done
      startLatch.await();
      System.out.println("Doing processing " + Thread.currentThread().getName());
      doneLatch.countDown();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Calling await ...
Calling await ...
Calling await ...
Calling await ...
Calling await ...
Calling await ...
Main thread - Doing setup
Doing processing Thread-1
Doing processing Thread-4
Doing processing Thread-3
Doing processing Thread-2
Doing processing Thread-0
Main thread - Proceed after all the threads are done
Doing processing Thread-5

As you can see from the output now main thread can start processing any time once the countdown reaches zero from three.

That's all for the topic CountDownLatch in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

CyclicBarrier in Java With Examples

The java.util.concurrent package provides many synchronizer utilities that cover the use cases where several thread communicate with each other and some sort of synchronizer is needed to regulate that interaction among the threads. CyclicBarrier in Java is one such synchronization aid that is useful when you want threads to wait at a common execution point until all the threads in the set have reached that common barrier point.

CyclicBarrier class in Java

CyclicBarrier class was added in Java 1.5 and it is part of java.util.concurrent package in Java. The class is named as CyclicBarrier because it can be reused after the waiting threads are released.

How CyclicBarrier works in Java

A CyclicBarrier is initialized with a given value and the barrier is tripped when the number of threads waiting at the barrier is equal to that value.

To make the thread wait at this barrier await() method is called upon the thread.

A thread calling the await() method signifies that the thread has reached the common barrier point and that thread is disabled until the required number of threads have invoked await() method at which point the barrier is tripped and the threads are unblocked. That way threads can synchronize their execution.

Java CyclicBarrier constructors

  • CyclicBarrier(int parties)- Creates a new CyclicBarrier that will trip when the given number of parties (threads) are waiting upon it.
  • CyclicBarrier(int parties, Runnable barrierAction)- Creates a new CyclicBarrier that will trip when the given number of parties (threads) are waiting upon it, and which will execute the given barrier action when the barrier is tripped, performed by the last thread entering the barrier.

Java CyclicBarrier methods

  1. await()- Waits until all parties have invoked await on this barrier.
  2. await(long timeout, TimeUnit unit)- Waits until all parties have invoked await on this barrier, or the specified waiting time elapses.
  3. getNumberWaiting()- Returns the number of parties currently waiting at the barrier.
  4. getParties()- Returns the number of parties required to trip this barrier.
  5. isBroken()- Queries if this barrier is in a broken state.
  6. reset()- Resets the barrier to its initial state.

CyclicBarrier Java example

In the example three threads are created and an ArrayList is shared among these threads. All of these threads process some data and put the results in the ArrayList. Further processing should start only after all the three threads have finished and called await().

For this scenario we’ll use a CyclicBarrier initialized with the value 3 and also a runnable action for further processing.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicDemo {
  public static void main(String[] args) {		
    List<Integer> dataList = Collections.synchronizedList(new ArrayList<Integer>());
    // Initializing cyclicbarrier
    CyclicBarrier cb = new CyclicBarrier(3, new ListBarrierAction(dataList));
    // starting threads
    for(int i = 0; i < 3; i++) {
      new Thread(new ListWorker(dataList, cb)).start();;
    }
  }    
}

class ListWorker implements Runnable{
  private CyclicBarrier cb;
  private List<Integer> dataList;
  ListWorker(List<Integer> dataList, CyclicBarrier cb) {
    this.dataList = dataList;
    this.cb = cb;
  }
  @Override
  public void run() {
    System.out.println("Executing run method for thread - " + Thread.currentThread().getName());
    for(int i = 0; i < 10; i++) {
      dataList.add(i);
    }
    
    try {
      System.out.println("Calling await.. " + Thread.currentThread().getName());
      cb.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (BrokenBarrierException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
// Barrier action to be executed when barrier is tripped
class ListBarrierAction implements Runnable {
  private List<Integer> dataList;
  ListBarrierAction(List<Integer> dataList){
    this.dataList = dataList;
  }
  @Override
  public void run() {
    System.out.println("In ListBarrierAction, start further processing on list with length " + dataList.size());
  }
}
Output
Executing run method for thread - Thread-0
Calling await.. Thread-0
Executing run method for thread - Thread-2
Executing run method for thread - Thread-1
Calling await.. Thread-2
Calling await.. Thread-1
In ListBarrierAction, start further processing on list with length 30

What happens when await method is called

When the await() method of CyclicBarrier class is called upon the current thread and the current thread is not the last to arrive then it is disabled for thread scheduling purposes and lies dormant until one of the following things happens-

  • The last thread arrives; or
  • Some other thread interrupts the current thread; or
  • Some other thread interrupts one of the other waiting threads; or
  • Some other thread times out while waiting for barrier; or
  • Some other thread invokes reset() on this barrier.

If the current thread is the last thread to arrive then-

If a non-null barrier action was supplied in the constructor, then the current thread runs the action before allowing the other threads to continue. If an exception occurs during the barrier action then that exception will be propagated in the current thread and the barrier is placed in the broken state.

BrokenBarrierException in CyclicBarrier

If any thread is interrupted while waiting at a barrier point, then all other waiting threads will throw BrokenBarrierException and the barrier is placed in the broken state.

If the barrier is reset() while any thread is waiting then BrokenBarrierException is thrown.

CyclicBarrier is reusable

Unlike one of the other synchronization aid CountDownLatch which can’t be reused, CyclicBarrier in Java can be reused after the waiting threads are released.

Let's reuse the same example as above but now the CyclicBarrier is initialized with the value 4 as we’ll wait for the main thread too. Three more threads will be started once the initial set of three threads are released from the barrier these new threads use the same CyclicBarrier object.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicDemo {
  public static void main(String[] args) {     
  List<Integer> dataList = Collections.synchronizedList(new ArrayList<Integer>());
  // Initializing cyclicbarrier
  CyclicBarrier cb = new CyclicBarrier(4, new ListBarrierAction(dataList));
  // starting threads
  for(int i = 0; i < 3; i++) {
    new Thread(new ListWorker(dataList, cb)).start();;
  }
  try {
    // Calling await for main thread
    cb.await();
  } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  } catch (BrokenBarrierException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
  System.out.println("done with initial set of threads, starting again reusing the same cyclicbarrier object");
  dataList = Collections.synchronizedList(new ArrayList<Integer>());
  // Starting another set of threads
  for(int i = 0; i < 3; i++) {
    new Thread(new ListWorker(dataList, cb)).start();;
  }    
  try {
    // Calling await for main thread
    cb.await();
  } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  } catch (BrokenBarrierException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
  System.out.println("In main thread...");
  }    
}

class ListWorker implements Runnable{
  private CyclicBarrier cb;
  private List<Integer> dataList;
  ListWorker(List<Integer> dataList, CyclicBarrier cb) {
    this.dataList = dataList;
    this.cb = cb;
  }
  @Override
  public void run() {
    System.out.println("Executing run method for thread - " + Thread.currentThread().getName());
    for(int i = 0; i < 10; i++) {
      dataList.add(i);
    }
    
    try {
      System.out.println("Calling await.. " + Thread.currentThread().getName());
      cb.await();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (BrokenBarrierException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
// Barrier action to be executed when barrier is tripped
class ListBarrierAction implements Runnable {
  private List<Integer> dataList;
  ListBarrierAction(List<Integer> dataList){
    this.dataList = dataList;
  }
  @Override
  public void run() {
    System.out.println("In ListBarrierAction, start further processing on list with length " + dataList.size());
  }
}
Output
Executing run method for thread - Thread-0
Executing run method for thread - Thread-1
Executing run method for thread - Thread-2
Calling await.. Thread-2
Calling await.. Thread-0
Calling await.. Thread-1
In ListBarrierAction, start further processing on list with length 30
done with initial set of threads, starting again reusing the same cyclicbarrier object
Executing run method for thread - Thread-4
Calling await.. Thread-4
Executing run method for thread - Thread-3
Executing run method for thread - Thread-5
Calling await.. Thread-5
Calling await.. Thread-3
In ListBarrierAction, start further processing on list with length 30
In main thread...

That's all for the topic CyclicBarrier in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Java ConcurrentHashMap With Examples

ConcurrentHashMap in Java is a thread safe Map implementation which provides another alternative to be used in a multithreaded environment apart from HashTable or explicitly synchronizing HashMap. ConcurrentHashMap is part of the java.util.concurrent package.

How is ConcurrentHashMap a better option

Other thread safe implementations like HashTable or explicit synchronization of HashMap synchronize all the methods on a single lock and all the methods are synchronized, even if methods are for retrieving elements. That makes these options very slow-

  1. Since whole collection is locked so only a single thread can access it at a given time.
  2. Since all the methods are synchronized so read operations are also slow.

ConcurrentHashMap in Java tries to address these issues-

  1. By not locking the collection for retrieval operations like get(). Concurrent read operations are permitted only the write operations are synchronized.
  2. Even for write operations whole collection is not locked but only the part of the table where the elements has to be put as per the calculated hashcode.

Internal implementation of ConcurrentHashMap in Java

For storing its element ConcurrentHashMap internally uses an array named table of type Node.

transient volatile Node<K,V>[] table;

Here Node class represents the key-value entry and defined as a static class with in the ConcurrentHashMap implementation. Node class has fields for storing key and value and also next field for holding the reference to the next node.

static class Node<K,V> implements Map.Entry<K,V> {
  final int hash;
  final K key;
  volatile V val;
  volatile Node<K,V> next; 
  .....

Here note that in initial implementation of ConcurrentHashMap in Java 5 there was array Segment which was used and that provided concurrency level of 16 by default i.e. 16 threads could access 16 elements stored in different indexes of the array because each segment could be locked independently. But Java 8 onward internal implementation of ConcurrentHashMap is modified and now array named table is used and it mainly uses Compare-And-Swap (CAS) operations for concurrency during write operations.

Each array index in the table can still be independently locked by synchronizing the first node of that particular bucket.

internal implementation ConcurrentHashMap in Java

Java ConcurrentHashMap constructors

  • ConcurrentHashMap()- Creates a new, empty map with the default initial table size (16).
  • ConcurrentHashMap(int initialCapacity)- Creates a new, empty map with an initial table size accommodating the specified number of elements without the need to dynamically resize.
  • ConcurrentHashMap(int initialCapacity, float loadFactor)- Creates a new, empty map with an initial table size based on the given number of elements (initialCapacity) and initial table density (loadFactor).
  • ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)- Creates a new, empty map with an initial table size based on the given number of elements (initialCapacity), table density (loadFactor), and number of concurrently updating threads (concurrencyLevel).
  • ConcurrentHashMap(Map<? extends K,? extends V> m)- Creates a new map with the same mappings as the given map.

Java example creating a ConcurrentHashMap

In this example a ConcurrentHashMap is created and (key, value) pair added to it which are later displayed.

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CHMExample {
  public static void main(String[] args) {
    // Creating ConcurrentHashMap
    Map<String, String> carMap = new ConcurrentHashMap<String, String>();
    // Storing elements
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    
    for (Map.Entry<String, String> entry : carMap.entrySet()) {
      System.out.println("key- " + entry.getKey() + " value- " + entry.getValue());
    }
  }
}
Output
key- 1 value- Audi
key- 2 value- BMW
key- 3 value- Jaguar
key- 4 value- Mini Cooper

Null not allowed in Java ConcurrentHashMap

ConcurrentHashMap does not allow insertion of null as either key or value. So both of the following statements result in NullPointerException.

carMap.put(null, "Audi");
Exception in thread "main" java.lang.NullPointerException
carMap.put("1", null);
Exception in thread "main" java.lang.NullPointerException

ConcurrentHashMap in Java is thread safe

ConcurrentHashMap in Java is safe to use in multi-threaded environment. Let’s see an example where we’ll first try to insert 400 elements in a HashMap (which is not thread safe) using 4 threads with each thread inserting 100 elements. Expected size of the HashMap is 400 after the execution.

import java.util.HashMap;
import java.util.Map;

public class MapSynchro implements Runnable{
  private Map<String, String> testMap;
  public MapSynchro(Map<String, String> testMap){
    this.testMap = testMap;
  }

  public static void main(String[] args) {
    Map<String, String> testMap = new HashMap<String, String>();
    /// 4 threads
    Thread t1 = new Thread(new MapSynchro(testMap));
    Thread t2 = new Thread(new MapSynchro(testMap));
    Thread t3 = new Thread(new MapSynchro(testMap));
    Thread t4 = new Thread(new MapSynchro(testMap));
        
    t1.start();
    t2.start();
    t3.start();
    t4.start();
        
    try {
      t1.join();
      t2.join();
      t3.join();
      t4.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of Map is " + testMap.size());
  }
  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 100; i++){
      // adding thread name to make element unique
      testMap.put(str+i, str+i);
      try {
        // delay to verify thread interference
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-3
in run methodThread-0
in run methodThread-1
in run methodThread-2
Size of Map is 394

As you can see size is 394 in one run because of the thread interference.

Using ConcurrentHashMap eliminates such inconsistencies. You just need to change the following line in the code.

Map<String, String> testMap = new ConcurrentHashMap<String, String>();

Now the size is always 400.

Java ConcurretHashMap returns a fail-safe iterator

Iterator returned by ConcurrentHashMap is fail-safe and does not throw ConcurrentModificationException if the map is structurally modified at any time after the iterator is created.

public class FailSafeDemo {
  public static void main(String[] args) {
    Map<String, String> carMap = new ConcurrentHashMap<String, String>();
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    // iterating map
    Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator();
    while(itr.hasNext()) {
      Map.Entry<String, String&gt; entry = itr.next();
      System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue());
      carMap.put("5", "Mercedes");
    }
    System.out.println("Size- " + carMap.size());
  }
}
Output
Key is 1 Value is Audi
Key is 2 Value is BMW
Key is 3 Value is Jaguar
Key is 4 Value is Mini Cooper
Key is 5 Value is Mercedes
Size- 5

In the code, while iterating the ConcurrentHashMap a new element is added to it that does not result in ConcurrentModificationException being thrown.

Atomic operations in ConcurrentHashMap

Even though Java ConcurrentHashMap is thread safe but still atomic operations may give inconsistent result in multi-threaded environment. For example a scenario as follows.

Integer oldVal = CHMMap.get(key); 
Integer newVal = (oldVal== null) ? 1 : oldVal + 1;
// newValue stored by another thread
CHMMap.put(key, newValue);

Here if an executing thread is preemepted by another thread after the execution of this line- Integer newVal = (oldVal== null) ? 1 : oldVal + 1; then the value put back in the ConcurrentHashMap may not be correct. In such scenarios it is better to use atomic operations. Some of the atomic operations in the ConcurrentHashMap class are-

  • putIfAbsent(K key, V value)- If the specified key is not already associated with a value, associates it with the given value.
  • remove(Object key, Object value)- Removes the entry for a key only if currently mapped to a given value.
  • computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)- If the specified key is not already associated with a value, attempts to compute its value using the given mapping function and enters it into this map unless null.
  • computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)- If the value for the specified key is present, attempts to compute a new mapping given the key and its current mapped value.
  • compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)- Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).
  • merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)- If the specified key is not already associated with a (non-null) value, associates it with the given value.
Using the atomic operation compute(), scenario as listed above can be written as follows-
CHMMap.compute(key, (k,v)-> v == null ? 1 : v + 1);

Advantages and disadvantages of ConcurrentHashMap

  1. ConcurrentHashMap in Java performs better if there are more reads than writes in a multi-threaded environment as the concurrent read operations are permitted. Since retrieval operations are non-blocking, so may overlap with update operations (including put and remove). Thus the concurrent retrievals may or may not reflect insertion or removal of some entries.
  2. If there are more writes and updates in the ConcurrentHashMap and HashCode implementation is not proper then many elements may have the same hashcode. In that scenario most of the threads will need to access the same table index where the elements with the same hashcode are to be stored resulting in degraded performance.
  3. Iterators in ConcurrentHashMap are designed to be used by only one thread at a time.

That's all for the topic Java ConcurrentHashMap With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Java CopyOnWriteArrayList With Examples

CopyOnWriteArrayList in Java implements the List interface just like its other well known counterpart ArrayList and is part of java.util.concurrent package. How CopyOnWriteArrayList differs from ArrayList is that it is a thread-safe variant of ArrayList.

For more differences between CopyOnWriteArrayList and ArrayList in Java refer this post- Difference Between ArrayList And CopyOnWriteArrayList in Java

How is Java CopyOnWriteArrayList thread-safe

CopyOnWriteArrayList in Java, just like ArrayList, uses an array of type Object to store its elements. For thread safety, implementation of CopyOnWriteArrayList, as its name suggests, creates a new copy of the underlying array for any modification operation like add, set, replace etc.

That makes CopyOnWriteArrayList a good choice when there are more traversal operations than the mutations, as the List can be iterated along with concurrent modifications with out any inference as the iteration will be done on a separate copy of the List.

Java CopyOnWriteArrayList constructors

  • CopyOnWriteArrayList()- Creates an empty list.
  • CopyOnWriteArrayList(Collection<? extends E> c)- Creates a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.
  • CopyOnWriteArrayList(E[] toCopyIn)- Creates a list holding a copy of the given array.

Java example creating a CopyOnWriteArrayList

Here is a simple example showing how to create CopyOnWriteArrayList and add elements to it.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new CopyOnWriteArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    System.out.println("List elements- " + carList);
  }
}
Output
List elements- [Audi, Jaguar, Mini Cooper, BMW]

CopyOnWriteArrayList returns a fail-safe iterator

Iterator returned by CopyOnWriteArrayList in Java is fail-safe which means iterator is guaranteed not to throw ConcurrentModificationException even if the List is structurally modified at any time after the iterator is created.

When an iterator is created for the CopyOnWriteArrayList it gets an immutable copy of the underlying array which is iterated. This array never changes during the lifetime of the iterator, so interference is impossible.

But note that since iteration is done on a separate copy so any modification in the CopyOnWriteArrayList won’t be reflected while iteration.

CopyOnWriteArrayList iteration example

Let’s see an example of iteration in CopyOnWriteArrayList. To make it clearer first we’ll iterate an ArrayList while it is also modified concurrently by another thread to see what happens in the case of ArrayList then we’ll see the same example using CopyOnWriteArrayList.

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new ArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    Thread t1 = new Thread(new ItrClass(carList));
    Thread t2 = new Thread(new ModClass(carList));
    t1.start();
    t2.start();
    try {
      t1.join();
      t2.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("List elements in Main- " + carList);
  }
}

// Thread class for iteration
class ItrClass implements Runnable{
  List<String> carList; 
  public ItrClass(List<String> carList){
    this.carList = carList;
  }
  @Override
  public void run() {
    Iterator<String> i = carList.iterator(); 
    while (i.hasNext()){ 
      System.out.println(i.next()); 
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } 
    }     
  }
}

//Thread class for modifying list
class ModClass implements Runnable{
  List<String> carList; 
  public ModClass(List<String> carList){
    this.carList = carList;
  }
  @Override
  public void run() {
    System.out.println("Adding new value to the list"); 
    carList.add("Mercedes");  
  }     
}
Output
Adding new value to the list
Audi
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at com.knpcode.ItrClass.run(CopyList.java:41)
	at java.base/java.lang.Thread.run(Thread.java:844)
List elements in Main- [Audi, Jaguar, Mini Cooper, BMW, Mercedes]

As you can see with ArrayList if list is modified while it is being iterated then the ConcurrentModificationException is thrown.

Using CopyOnWriteArrayList

Now we can change the ArrayList to CopyOnWriteArrayList in the same example.

List<String> carList = new CopyOnWriteArrayList<String>();

With that output is

Adding new value to the list
Audi
Jaguar
Mini Cooper
BMW
List elements in Main- [Audi, Jaguar, Mini Cooper, BMW, Mercedes]

As you can see now ConcurrentModificationException is not thrown but the iterator doesn’t display the newly added element as it is iterating on a separate copy.

Iterator’s add, remove method not permitted in CopyOnWriteArrayList

Making a new copy gives you the convenience to iterate the List without the fear of ConcurrentModificationException but at the same time iterator’s element-changing operations like remove, set, and add are not supported in CopyOnWriteArrayList. These methods throw UnsupportedOperationException.

public class CopyList {
  public static void main(String[] args) {
    //creating CopyOnWriteArrayList
    List<String> carList = new CopyOnWriteArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("Mini Cooper");
    carList.add("BMW");
    Iterator<String> itr = carList.iterator(); 
    while (itr.hasNext()){ 
      String str = itr.next();
      if(str.equals("Jaguar")) {
        // removing using iterator's remove method
        itr.remove();
      }
    }
  }
}
Output
Exception in thread "main" java.lang.UnsupportedOperationException 
at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1117)
at com.knpcode.CopyList.main(CopyList.java:21)

As you can see using iterator’s remove method here results in UnsupportedOperationException being thrown.

Advantages and disadvantages of using CopyOnWriteArrayList in Java

CopyOnWriteArrayList performs well when there are more traversal operations than the mutations, since you don't need to explicitly synchronize the CopyOnWriteArrayList to iterate it in a multi-threaded environment.

In general using CopyOnWriteArrayList is costly because of the added task of creating copies in case of mutative operations and keep changing the underlying array.

CopyOnWriteArrayList is guaranteed to not throw ConcurrentModificationException even if there are concurrent modifications to the list while iteration. At the same time iterator’s element-changing operations are not supported.

That's all for the topic Java CopyOnWriteArrayList With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Runnable Vs Callable in Java

Java programming language provides two ways to implement a class whose instances are to be executed by a thread.

  • By implementing Runnable interface
  • By implementing Callable interface

Since there are two options so they must have some differences in the features they offer, that’s what we’ll discuss in this post; differences between Runnable and Callable in Java.

Runnable Vs Callable in Java

1- Part of Java programming language Runnable interface is there since Java 1.0. Callable interface is added in Java 1.5

2- Interface method

Runnable interface has a single method run() with the method signature as following.

public interface Runnable {
  public abstract void run();
}

Callable interface has a single method call(), a generic method with the method signature as following.

public interface Callable<V> { 
  V call() throws Exception;
}

3- Return value

This is one of major difference between Runnable and Callable in Java. Runnable doesn’t return a result. You can see from the method signature of the run method that the return type is void.

Callable can return a result. Return type of the result is provided at the time of creating Callable instance.

For Example a Callable that returns String.

Callable<String> callableObj = new Callable<String>() {
  @Override
  public String call() throws Exception {
    return "Hello";
  }
};

4- Exception handling

Since Runnable’s run method doesn’t include a throws clause so it is not possible for a Runnable to throw a checked exception.

Following implementation of Runnable results in compile time error “Exception IOException is not compatible with throws clause in Runnable.run()” as IOException is a checked exception.

Runnable runnable = new Runnable() {
  @Override
  public void run() throws IOException{
	  
  }
};

But it is ok if RunTimeException is thrown.

public void run() throws RuntimeException{

}

Callable’s call method does include a throws clause- V call() throws Exception; so it is possible for a Callable to throw a checked exception.

Callable<String> callable = new Callable<String>() {
  @Override
  public String call() throws InterruptedException {
    return "Hello";
  }
};

5- How to execute.

To execute a Runnable task you can use one of the following options-

  1. Thread class has a constructor where you can pass a Runnable as a parameter.
  2. You can use execute method of the Executor interface to run a Runnable task.
  3. You can use submit method of the ExecutorService interface to run a Runnable task.

To execute a Callable task there is no support provided by the Thread class but the concurrent API provides the following options-

  1. You can use submit method of the ExecutorService interface to run a Callable task.
  2. ExecutorService also has invokeAll() and invokeAny() methods where Callable is passed as a parameter.
  3. You can also convert a Runnable object to Callable object by using Executors.callable(Runnable task) provided by utility class Executors.
That's all for the topic Runnable Vs Callable in Java. If something is missing or you have something to share about the topic please write a comment.
You may also like

Input from User in Python – input() function

If you have to take user input from keyboard in Python that can be done using input() function. Python input() function takes the entered value and returns it as a string once user presses Enter.

Syntax of input() function

The syntax of Python input() function is

input(msg)

Here msg is the optional string that is displayed on the console.

User input in Python example

val = input('Please enter your name: ')
print('Entered Value- ', val)
Output
Please enter your name: knpcode
Entered Value- knpcode

Input as number in Python

Note that input() function always returns entered value as string. If you want to get the value as integer or float you need to convert it explicitly.

val = input('Please enter a number: ')
# Convert to int
val = int(val)
# now use in arithmetic operation
print(val+5)
Output
Please enter a number: 8
13

You can use int() function along with the input() function to shorten the code-

val = int(input('Please enter a number: '))
# now use in arithmetic operation
print(val+5)

For float numbers you can use float() function

val = float(input('Please enter a number: '))
# now use in arithmetic operation
print(val+5)

Multiple values as input in Python

You can write separate input() functions to take more than one inputs from user. But Python provides a shorter version by using for loop along with input() function to take multiple inputs. Entered values are parsed as separate values by using split() method which considers space as delimiter by default. Therefore separate the separate inputs by space.

num1, num2, num3 = [int(x) for x in input('Please enter three integers: ').split()]
print('Sum of entered numbers-', (num1+num2+num3))
Output
Please enter three integers: 4 5 6
Sum of entered numbers- 15

That's all for the topic Input from User in Python – input() function. If something is missing or you have something to share about the topic please write a comment.


You may also like

Python Program to Append to a File

In the post Python Program to Write a File we saw options to write to a file in Python but that has the drawback of overwriting the existing file. If you want to keep adding content to an existing file you should use append mode to open a file. In this tutorial we’ll see options to append to a file in Python.

Append mode in Python I/O

To append data to a file i.e. adding content to the end of an existing file you should open file in append mode (‘a’). If the file doesn’t exist it will create a new file for writing content.

Appending to a file in Python

Following method opens the passed file in append mode and then adds content to the end of the file.

def append_file(fname):
  with open(fname, 'a') as f:
    f.write('This line is added to the already existing content')

Using ‘a+’ mode to write and read file

Following program opens a file in ‘a+’ mode for both appending and reading. Program also uses tell() method to get the current position of the file pointer and seek() method to move to the beginning of the file.

def append_file(fname):
  with open(fname, 'a+') as f:
    f.write('This line is added to the already existing content')
    f.flush()
    print("Current position of file pointer- ", f.tell())
    f.seek(0, 0)
    s = f.read()
    print('Content- ', s)

That's all for the topic Python Program to Append to a File. If something is missing or you have something to share about the topic please write a comment.


You may also like

Python Program to Write a File

In this tutorial we’ll see different options to write to a file in Python.

  1. Using write() method you can write the passed string to a file.
  2. Using writelines(lines) method you can write a list of lines.
  3. Writing file in binary mode.

1. Using write() method for file writing in Python

f.write(string) writes the contents of string to the file and returns the number of characters written. For writing to a file in Python file should be opened in write mode. Note that opening in write mode (‘w’) will either create the file if it doesn’t exist or overwrite the file if already exists.

def write_file(fname):
  try:
    f = open(fname, 'w')
    f.write("This is Line 1.\n")
    f.write("This is Line 2.")
  finally:
    f.close()


write_file("F:/knpcode/Python/test.txt")

Here write_file() method takes the file path as argument and opens that file in write mode. Two lines are written to the file and then the file is closed.

Another way to open the file is using with keyword which automatically closes the file. Using with open is preferred as it makes code shorter.

def write_file(fname):
  with open (fname, 'w') as f:
    chars_written = f.write("This is Line 1.\n")
    print('Characters written to the file', chars_written);
    f.write("This is Line 2.")

As you can see now try-finally block is not required as with open automatically closes the file once file operations are done.

If you want to write any other type of object then it has to converted to the string (in text mode) or a bytes object (in binary mode) before writing it to the file. For example in the following program we want to write a tuple to the file for that it has to be converted to str first.

def write_file(fname):
  with open(fname, 'w') as f:
    value = ('One', 1)
    s = str(value)  # convert the tuple to string
    f.write(s)

2. Using writelines(lines) method you can write a list of lines.

If you have a list of lines then you can use writelines() method to write it.

def write_file(fname):
  with open(fname, 'w') as f:
    lines = ["This is the first line\n", "This is the second line\n", "This is the third line"]
    f.writelines(lines)

3. Using ‘w+’ mode to write and read file.

Following program opens a file in 'w+' mode for both writing and reading. Program also uses tell() method to get the current position of the file pointer and seek() method to move to the beginning of the file.

def write_read_file(fname):
  with open(fname, 'w+') as f:
    f.write("This is the first line.\n")
    f.write("This is the second line.\n")
    f.flush()
    print("Current position of file pointer- ", f.tell())
    f.seek(0, 0)
    s = f.read()
    print('Content- ', s)

4. Writing a binary file in Python

If you want to write a binary file you need to open file in ‘wb’ mode. In the following Python program to copy an image an image file is opened in binary mode and then written to another file.

def copy_file():
  try:
    # Opened in read binary mode
    f1 = open('F:/knpcode/Java/Java Collections/collection hierarchy.png', 'rb')
    # Opened in write binary mode
    f2 = open('F:/knpcode/Python/newimage.png', 'wb')
    b = f1.read()
    f2.write(b)
    print('Coying image completed...')
  finally:
    f1.close()
    f2.close()

That's all for the topic Python Program to Write a File. If something is missing or you have something to share about the topic please write a comment.


You may also like

Python Program to Read a File

In this tutorial we’ll see different options to read a file in Python.

  1. Using read() method you can read the whole file.
  2. Using readline() and readlines() methods you can read file line by line.
  3. More efficient way to read file line by line is to iterate over the file object.
  4. Reading file in binary mode.

1. Using read() method

f.read(size) method reads and returns size bytes. If size argument is not passed or negative, the entire contents of the file will be read and returned.

def read_file(fname):
  try:
    f = open(fname, 'r')
    s = f.read()
    print(s)
  finally:
    f.close()


read_file('F:\\knpcode\\abc.txt')

Here read_file() function is written to read a file which takes file path as argument. File is opened using open() function in read mode and read using read() method. You should always close the file to free the resources which is done in the finally block.

Another way to open the file is using with keyword which automatically closes the file. Using with open is preferred as it makes code shorter.

def read_file(fname):
  with open(fname, 'r') as f:
    s = f.read(9)
    print(s) 

2. Using readline() method to read a file in Python.

f.readline() reads a single line from the file.

def read_file(fname):
  with open(fname, 'r') as f:
    s = f.readline()
    print('Content- ', s)
3. Using readlines() method.

f.readlines() method reads all the lines of a file in a list.

def read_file(fname):
  with open(fname, 'r') as f:
    s = f.readlines()
    print('Content- ', s)

You can also read all the lines of a file by using list(f) function.

def read_file(fname):
  with open(fname, 'r') as f:
    s = list(f)
    print('Content- ', s)

4. Looping over the file object

read(size) or f.readlines() read all the content of the file making it inefficient if the file is large as the whole file will be loaded into the memory. More memory efficient and fast way to read lines from a file is by looping over the file object.

def read_file(fname):
  with open(fname, 'r') as f:
    for line in f:
      # Empty string (‘’) is the EOF char
      print(line, end='')

Similar logic to read the file line by line in Python can also be written using the readline() method.

def read_file(fname):
  with open(fname, 'r') as f:
    line = f.readline()
    while line != '':
      print(line, end='')
      line = f.readline()

5. Reading a binary file in Python.

If you want to read a binary file you need to open file in ‘rb’ mode. In the following Python program to copy an image an image file is opened in binary mode and then written to another file.

def copy_file():
  try:
    f1 = open('F:/knpcode/Java/Java Collections/collection hierarchy.png', 'rb')
    f2 = open('F:/knpcode/Python/newimage.png', 'wb')
    b = f1.read()
    f2.write(b)
    print('Coying image completed...')
  finally:
    f1.close()
    f2.close()

That's all for the topic Python Program to Read a File. If something is missing or you have something to share about the topic please write a comment.


You may also like

Python I/O - Open, Read, Write Files

In this tutorial you will learn about Python I/O, how to open a file and how to read, write a file in Python.

File types supported by Python

Python has support for following types of files-

  1. Text files- Files where data is stored in the form of characters.
  2. Binary files- Files where data is stored in the form of bytes. Useful for storing images, serializing objects.

Opening a file

File in python is opened using built-in function open() which returns a file object. If the file cannot be opened, an OSError is raised. Syntax of open is as follows.

file_object = open(“filename”, “open_mode”, “buffering”, “encoding”)

Description of arguments-
  1. filename is the path (absolute or relative to the current working directory) of the file to be opened.
  2. open_mode specifies the mode in which the file is opened. It is an optional argument and defaults to 'r' which means open for reading in text mode.
  3. buffering specifies the buffering policy.
    • 0 means buffering is off (only allowed in binary mode)
    • 1 means line buffering (only usable in text mode)
    • integer > 1 indicates buffer size in bytes.
    It is also an optional argument and default buffer size is 4096 or 8192 bytes.
  4. encoding specifies the name of the encoding used to decode or encode the file. This should only be used in text mode.

Modes for opening a file in Python

Mode Description
'r' Open file for reading, starts from the beginning of the file. It is the default mode.
'w' Open file for writing. Any existing data in the file is deleted
'x' Opens for exclusive creation, failing if the file already exists
'a' Open for appending content to an already existing file. If file doesn’t exist a new file is created.
'b' Opens in binary mode
't' Opens in text mode, it is the default mode.
'+' open for updating (reading and writing)

Some examples of using open() function in Python.

# opens file using default mode which is ‘rt’ meaning reading a text file
f  = open(“abc.txt”)  

# opens file for writing in binary mode
f = open(“abc.txt”, 'wb')  

# opens file for writing and reading in binary mode
f = open(“abc.txt”, 'w+b') 

# opens file for reading and writing
f = open(“abc.txt”, 'r+')  

# opens file for writing, existing content is deleted
f = open(“abc.txt”, 'w')  

Closing a file

Once you are done with file I/O operations you should close the file using the close() method. Closing a file immediately frees up any system resources used by it.

f = open("abc.txt", 'a') # file operations .. .. f.close()

After a file object is closed, file object is not accessible any more. If you want to work with the file again, you need to open the file again using the open() function.

Python example to append to a file

Here is an example that opens a file in append mode and append content to the end of the file. Later same file is opened in read mode to read the file.

def write_file(fname):
  try:
    f = open(fname, 'a')
    f.write("This is a test file.\n")
  finally:
    f.close()


def read_file(fname):
  try:
    f = open(fname, 'r')
    str = f.read()
    print('Content- ', str)
  finally:
    f.close()


write_file('F:\\knpcode\\abc.txt')
read_file('F:\\knpcode\\abc.txt')

With Open

In the above example you can see there is a lot of code just to ensure that the file is closed properly. But that try-finally block is required too to ensure that the file is always closed and the resources freed even if an exception is raised.

Another way and the recommended one is to use with keyword in Python when using file objects. By opening a file using with statement file is closed automatically after its suite finishes, even if an exception is raised at some point.

Syntax of using with is as follows-

with open(“file name”, “open mode”) as fileobject:

using with open Python example
def read_file(fname):
    with open(fname, 'r') as f:
        s = f.read()
        print('Content- ', s)
    print('File closed- ', f.closed)


read_file('F:\\knpcode\\abc.txt')

Using f.closed() you can check that the file has been automatically closed.

Python File methods

Here is a complete list of methods in class io.TextIOBase which is the base class for text streams. These are the methods you will use when you get a file object by opening a file in text mode.

Method Description
close() Flush and close this stream.
closed() True if the stream is closed.
fileno() Return a file descriptor (an integer) of the stream
flush() Flush the write buffers of the stream if applicable.
isatty() Return True if the stream is interactive
read(n=-1) Read and return at most n characters from the stream as a single str. If n is negative or None, reads until EOF.
readline(n=-1) Read until newline or EOF and return a single str. If n is specified, at most n characters will be read.
readlines() Read and return a list of lines from the stream.
readable() Return True if the stream can be read from.
seek(offset, fromwhere) Change the stream position to the given byte offset. offset is interpreted relative to the position indicated by fromwhere.
seekable() Return True if the stream supports random access.
tell() Return the current stream position.
truncate(size=None) Resize the stream to the given size in bytes.Resizes to current location if size is not specified.
writable() Return True if the stream supports writing.
write(s) Write the string s to the stream and return the number of characters written.
writelines(lines) Write a list of lines to the stream.

That's all for the topic Python I/O - Open, Read, Write Files. If something is missing or you have something to share about the topic please write a comment.


You may also like

Spring Boot With Docker Example

In this tutorial you’ll see how to build a Docker image for running a Spring Boot application. We’ll create a basic DockerFile to dockerize a Spring Boot MVC application where view is created using Thymeleaf.

Maven Dependencies

Since we are creating a web application so we need a spring-boot-starter-web, for Thymeleaf we need spring-boot-starter-thymeleaf, spring-boot-maven-plugin is also added to our pom.xml. This plugin provides many convenient features-

  • It helps to create an executable jar (├╝ber-jar), which makes it more convenient to execute and transport your service.
  • It also searches for the public static void main() method to flag the class having this method as a runnable class.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.knpcode</groupId>
  <artifactId>SprinBootProject</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringBootProject</name>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
     </dependency>
     <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Classes for Spring Boot Web Application

We’ll add a simple controller for our web application.

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MessageController {
  @GetMapping("/")
  public String showMessage(Model model) { 
    model.addAttribute("msg", "Welome to Docker");
    return "message";
  }
}
View class (Thymeleaf template)

In src/main/resources added a new folder Templates and in that created a message.html file.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring Boot With Docker</title>
</head>
<body>
 <div>
    <p th:text="${msg}"></p>
 </div>
</body>
</html>

Application Class

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootProjectApp {
  public static void main(String[] args) {
    SpringApplication.run(SpringBootProjectApp.class, args);
  }
}
Running the application

You can run this Spring Boot web application as a stand alone Java application but we'll run it by creating an executable jar.

For creating a completely self-contained executable jar file run mvn package from the command line. Note that you should be in your Spring Boot project directory.

knpcode:SprinBootProject$ mvn package

To run application using the created jar, you can use the java -jar command, as follows-

java -jar target/SprinBootProject-0.0.1-SNAPSHOT.jar

But we’ll do the samething by creating a DockerFile.

DockerFile

For running in your application in Docker container you need to create an image which is a read-only template with instructions for creating a Docker container.

For creating Docker image you create a Dockerfile which is a text file with a simple syntax for defining the steps needed to create the image and run it. Each instruction in a Dockerfile creates a layer in the image.

Create a text file with in your project directory named DockerFile and copy the following text in it.

FROM openjdk:8-jdk-alpine

ARG JAR_FILE=target/SprinBootProject-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]
  1. Often, an image is based on another image, with some additional customization. This is true in our case too and the base image used here is openjdk:8-jdk-alpine This image is based on the popular Alpine Linux project which is much smaller than most distribution base images (~5MB), and thus leads to much slimmer images in general.
  2. Then assign a name to the jar path.
  3. Copy jar file.
  4. Execute jar using the ENTRYPOINT instruction by providing arguments in the following form- ENTRYPOINT ["executable", "param1", "param2"] Which makes it equivalent to java -jar target/SprinBootProject-0.0.1-SNAPSHOT.jar

Create a docker image

You can create a Docker image by running command in the following form-

sudo docker build -t name:tag .

For our project command to create a docker image-

sudo docker build -t sbexample:1.0 .

. means using the current directory as context

tag the image as sbexample:1.0

To create a container (run an image)

The docker run command must specify an image to derive the container from.

sudo docker run -d -p 8080:8080 sbexample:1.0

Here options are-

-d To start a container in detached mode (to run the container in the background)

-p Publish all exposed ports to the host interfaces

If every thing works fine then you will have a dockerized Spring Boot application at this point which you can access by typing URL http://localhost:8080/ in a browser

Spring Boot With Docker

If you want to see the running containers use following command

sudo docker ps

To stop a running container use following command

sudo docker stop container_id

That's all for the topic Spring Boot With Docker Example. If something is missing or you have something to share about the topic please write a comment.


You may also like