Open In App

Effective Utilization of TreeMap in Concurrent Java Applications

Last Updated : 27 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

A Java application may run many threads concurrently thanks to multithreading. Thread safety must be guaranteed when utilizing data structures like TreeMap in a multithreaded environment to prevent race situations. The recommended techniques for using TreeMap in multithreaded applications and avoiding race situations are covered in this tutorial.

TreeMap in a Multithreaded Environment

Thread safety is not inherent to TreeMap. Multiple threads altering a TreeMap concurrently may result in race situations and unexpected behavior. You may deal with this by using thread-safe alternatives such as ConcurrentNavigableMap, which is part of the java.util.concurrent package, or by using synchronization techniques.

Best Practices

  • Synchronization: To manage access to TreeMap, use appropriate synchronization techniques.
  • Thread-safe Alternatives: For concurrent access, take into account using thread-safe alternatives such as ConcurrentNavigableMap.
  • Immutable Objects: To prevent problems with data mutation, if at all feasible, utilize immutable objects within the TreeMap.

Java Program to Use TreeMap in Multithreaded Applications Using Synchronization

Below is the code implementation to use TreeMap in multithreaded applications and avoid race conditions.

1. Using Locks for Thread-Safe TreeMap

The main issue with utilizing TreeMap in a multithreaded context is the potential for race situations when many threads attempt to edit the map at the same time. Locks are one way to get around this.

Java




// Java program to Demonstrate Thread-Safe
// TreeMap Using ReadWriteLock
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
  
// Driver Class
public class ThreadSafeTreeMapExample 
{
    // ReadWriteLock for synchronization
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    // TreeMap to store key-value pairs
    private final Map<String, Integer> threadSafeTreeMap = new TreeMap<>();
  
    // Method to add key-value pairs to the TreeMap
    public void addToMap(String key, int value) 
    {
          // Acquire write lock
        lock.writeLock().lock(); 
        try{
              // Add key-value pair
            threadSafeTreeMap.put(key, value); 
            System.out.println("Added: " + key + ", " + value);
        } finally {
              // Release write lock
            lock.writeLock().unlock(); 
        }
    }
  
    // Method to get value for a given key from the TreeMap
    public int getFromMap(String key) 
    {
          // Acquire read lock
        lock.readLock().lock(); 
        try {
             // Get value for key or return default value (-1)
            return threadSafeTreeMap.getOrDefault(key, -1); 
        } finally {
              // Release read lock
            lock.readLock().unlock(); 
        }
    }
  
      // Main Function
    public static void main(String[] args) 
    {
        ThreadSafeTreeMapExample example = new ThreadSafeTreeMapExample();
  
        // Example usage: add key-value pairs in separate threads
        new Thread(() -> example.addToMap("A", 1)).start();
        new Thread(() -> example.addToMap("B", 2)).start();
  
        // Display values for keys "A" and "B"
        System.out.println("Value for A: " + example.getFromMap("A"));
        System.out.println("Value for B: " + example.getFromMap("B"));
    }
}


Output

Added: A, 1
Added: B, 2
Value for A: 1
Value for B: 2



Explanation of the above Program:

  • We have used a ReadWriteLock for synchronization to allow multiple threads to read simultaneously while ensuring exclusive write access.
  • Then, the addToMap() method adds key-value pairs to the TreeMap while holding the write lock.
  • After that, the getFromMap() method retrieves the value for a given key from the TreeMap while holding the read lock.
  • The main method demonstrates the usage of the ThreadSafeTreeMapExample class by adding key-value pairs in separate threads and then retrieving their values.

2. Using ConcurrentHashMap with TreeMap

Combining a ConcurrentHashMap with a TreeMap is an additional strategy. This leverages ConcurrentHashMap’s concurrent writing capabilities while enabling thread-safe read operations on the TreeMap.

Java




// Java Program to Utilize of TreeMap
// in Concurrent Java Applications
// Using ConcurrentHashMap with TreeMap
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
  
// Driver Class
public class ConcurrentMapWithTreeMapExample {
    // ConcurrentSkipListMap for concurrent access
    private final ConcurrentSkipListMap<String, Integer>
        concurrentMap = new ConcurrentSkipListMap<>();
    // ExecutorService for asynchronous execution
    private final ExecutorService executorService
        = Executors.newFixedThreadPool(2);
  
    // Method to add key-value pair to the concurrent map
    // asynchronously
    public void addToMap(String key, int value)
    {
        CompletableFuture.runAsync(() -> {
            addEntryToMap(key, value);
        }, executorService);
    }
  
    // Method to get value for a given key from the
    // concurrent map
    public int getFromMap(String key)
    {
        return concurrentMap.getOrDefault(key, -1);
    }
  
    // Method to get an ordered copy of the concurrent map
    public ConcurrentSkipListMap<String, Integer> getOrderedMap()
    {
        return new ConcurrentSkipListMap<>(concurrentMap);
    }
  
    // Private method to add entry to the concurrent map
    private void addEntryToMap(String key, int value)
    {
        concurrentMap.put(key, value);
        System.out.println("Added: " + key + ", " + value);
    }
  
    // Main Function
    public static void main(String[] args)
    {
        ConcurrentMapWithTreeMapExample example
            = new ConcurrentMapWithTreeMapExample();
  
        // Add key-value pairs asynchronously
        CompletableFuture<Void> futureA
            = CompletableFuture.runAsync(()
                    -> example.addToMap("A", 1),
                example.executorService);
          
          CompletableFuture<Void> futureB
            = CompletableFuture.runAsync(()
                    -> example.addToMap("B", 2),
                example.executorService);
  
        // Wait for all tasks to complete
        CompletableFuture.allOf(futureA, futureB).join();
  
        // Display values for keys "A" and "B"
        System.out.println("Value for A: " + example.getFromMap("A"));
        System.out.println("Value for B: " + example.getFromMap("B"));
  
        // Get an ordered copy of the concurrent map
        ConcurrentSkipListMap<String, Integer> orderedMap
            = example.getOrderedMap();
        
        System.out.println("Ordered Map: " + orderedMap);
  
        // Shut down the executor service
        try {
            example.executorService.shutdown();
        }
        finally {
        }
    }
}


Output

Value for A: 1
Value for B: 2
Ordered Map: {A=1, B=2}
Added: B, 2
Added: A, 1



Explanation of the above Program:

  • We have used the ConcurrentSkipListMap that allows concurrent access to the map.
  • ExecutorService with a fixed thread pool size of 2 is used for asynchronous execution of tasks.
  • The addToMap method adds key-value pairs to the map asynchronously using CompletableFuture.runAsync.
  • The getFromMap method retrieves the value for a given key from the concurrent map.
  • The getOrderedMap method returns an ordered copy of the concurrent map.
  • In the main method, key-value pairs are added asynchronously, and their values are displayed. An ordered copy of the map is also obtained and displayed. Finally, the executor service is shut down.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads