paint-brush
Understanding Synchronized Collections in Javaby@infinity
617 reads
617 reads

Understanding Synchronized Collections in Java

by Rishabh AgarwalFebruary 25th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Java’s Collection framework is one of the foundational components of Java platform. Collections in Java represent a group of objects and the framework provides several interfaces and implementations to work with these collections. With the advent of multi-threaded programming, the need for thread-safe collections grew. It is when*Synchronized Collections* were added to the Collection framework that they were made thread- safe.
featured image - Understanding Synchronized Collections in Java
Rishabh Agarwal HackerNoon profile picture


Java’s Collection framework is one of the foundational components of the Java platform. Collections in Java represent a group of objects and the framework provides several interfaces and implementations to work with these collections. With such a rich framework, developers can focus on what matters the most!


With the advent of multi-threaded programming, the need for thread-safe collections grew. It is when Synchronized Collections were added to Java’s Collection framework. Let us understand what makes Synchronized Collections thread-safe, and some things we should keep in mind while using them.

How do Synchronized Collections ensure thread safety?

Synchronized collections achieve thread-safety by enforcing synchronization on each of its publicly available methods. In addition to that, it ensures that its internal state is never published. Thus, the only way to modify a collection is via its public synchronized methods!


Think of synchronized collections as plain unsynchronised collections plus state encapsulation and synchronized public methods.


Since the same (intrinsic) lock guards every public method of a synchronized collection, no two threads can modify/read the collection at once. This ensures that the collection always maintains its variants and hence becomes thread-safe.

How to create Synchronized Collections

The Collections class exposes several static methods for creating synchronized collections. These static methods are named in the following format — synchronizedXxx.


Here is a list of these methods:


  • synchronizedCollection(Collection<T> c)
  • synchronizedList(List<T> list)
  • synchronizedMap(Map<K, V> m)
  • synchronizedNavigableMap(NavigableMap<K, V> m)
  • synchronizedNavigableSet(NavigableSet<T> s)
  • synchronizedSet(Set<T> s)
  • synchronizedSortedMap(SortedMap<K, V> m)
  • synchronizedSortedSet(SortedSet<T> s)


This is how you will create a synchronized list using the Collections class!


List<Integer> synchronizedIntegerList = Collections.synchronizedList(new ArrayList<>());


Pitfall of Compound Actions on Synchronized Collections

While methods exposed from synchronized collections are thread-safe, compound actions on client side still require proper locking. Consider the following method —


public void putIfAbsent(List<Integer> synchronizedList, Integer elem) {
  if(!synchronizedList.contains()) {
    synchronizedList.add(elem);
  }
}


This innocent-looking method is thread-unsafe! Even with thread-safe contains and add methods, the putIfAbsent method does not achieve what it is intended to. Between the ‘check’ and ‘act’ steps of our method, another thread can add the elem to the list. Guarding such compound actions is the responsibility of the client.


Synchronized collections allow client-side locking to ensure that such compound actions are atomic concerning other operations. Each public method of the synchronized collection is guarded by its intrinsic lock. Thus, the client can create atomic operations by acquiring the same lock.


Here is how we will fix putIfAbsent using client-side locking.


public void putIfAbsent(List<Integer> synchronizedList, Integer elem) {
  synchronized(synchronizedList) {
    if(!synchronizedList.contains()) {
      synchronizedList.add(elem);
    }
  }
}


Pitfalls of Iterating on Synchronized Collections

Some extra care is needed to be taken when iterating over a synchronized collection. When iterating over a synchronized collection using iterators, any concurrent modifications will cause the iterator to fail fast. On detecting any concurrent modification, the iterator will throw a ConcurrentModificationException.


final Set<String> syncStringSet = Collections.synchronizedSet(new HashSet<>());

// Might throw ConcurrentModificationException
for(String s: syncStringSet) {
  doSomething(s);
}


To avoid getting a ConcurrentModificationException, we can hold a lock on the synchronized collection for the entire duration of the iteration. Clearly, this is not the most performant approach since the iteration can take a long time and it will block other executing threads from accessing the collection!


Synchronized classes provide an easy mechanism to make your collection classes thread-safe. A thorough understanding of these would help you avoid common pitfalls associated with it.


With this, we reach the end of this blog. I hope you enjoyed reading this piece.


Also published here.