Thread safety with ConcurrentHashMap

11,371

Solution 1

However, when I read from it and then remove all entries by clear() method, I want no other thread writes to map while I’m in the process of reading the map contents and then removing them.

I think what you're trying to say is that you don't really care about strictly locking the maps. Instead, you only really care about the loss of any log entries between the vender1Calls.values() and vendor1Calls.clear(), correct?

In that is the case, I can imagine that you can replace

events.addAll(vendor1Calls.values());
vendor1Calls.clear();

with this in saveEntries:

for (Iterator<Event> iter = vendor1Calls.values().iterator(); iter.hasNext(); ) {
    Event e = iter.next();
    events.add(e);
    iter.remove();
}

That way, you only remove the Events that you added to the events List already. You can still write to the vendor1Calls maps while saveEntries() is still executing, but the iterator skips the values added.

Solution 2

Without any external synchronization you cannot achieve this with a CHM. The Iterator views returned are weakly consistent which means the contents of the Map can change while you are actually iterating over it.

It appears you would need to use a Collections.synchronizedMap to get the functionality you are looking for.

Edit to make my point more clear:

To achieve this with a synchronizedMap You would first have to synchronize on the Map and then you can iterate or copy the contents into another map an then clear.

Map map = Collections.synchronizedMap(new HashMap());

public void work(){
  Map local = new HashMap();
  synchronized(map){
     local.putAll(map);
     map.clear();
  }
  //do work on local instance 
}

Instead of the local instance, as I mentioned, you can iterate + remove similar to @Kevin Jin's answer.

Share:
11,371
blueSky
Author by

blueSky

Updated on August 10, 2022

Comments

  • blueSky
    blueSky over 1 year

    I have the following class. I use ConcurrentHashMap. I have many threads writing to the maps and a Timer that saves the data in the map every 5 minutes. I manage to achieve thread safety by using putIfAbsent() when I write entries in the map. However, when I read from it and then remove all entries by clear() method, I want no other thread writes to map while I’m in the process of reading the map contents and then removing them. Obviously my code is not threadsafe even with synchronized(lock){}, b/c the thread that owns the lock in saveEntries(), is not necessarily the same thread that writes into my maps in log() method! Unless I lock the whole code in log() with the same lock object!

    I was wondering is there any other way to achieve thread safety w/o enforcing synchronizing by an external lock? Any help is greatly appreciated.

    public class Logging {
    
    private static Logging instance;    
    private static final String vendor1 = "vendor1";
    private static final String vendor2 = "vendor2";    
    private static long delay = 5 * 60 * 1000;
    
    private ConcurrentMap<String, Event> vendor1Calls = new ConcurrentHashMap<String, Event>();
    private ConcurrentMap<String, Event> vendor2Calls = new ConcurrentHashMap<String, Event>();
    
    private Timer timer;    
    private final Object lock = new Object();
    
    private Logging(){
        timer = new Timer();                
        timer.schedule(new TimerTask() {
            public void run() {
                try {
                    saveEntries();
                } catch (Throwable t) {
                    timer.cancel();
                    timer.purge();
                }
            }       
        }, 0, delay);
    }
    
    public static synchronized Logging getInstance(){     
        if (instance == null){
            instance = new Logging();
        }
        return instance;
     }
    
    public void log(){      
        ConcurrentMap<String, Event> map;
        String key = "";        
    
        if (vendor1.equalsIgnoreCase(engine)){
            map = vendor1Calls;
        }else if(vendor2.equalsIgnoreCase(engine)){  
            map = vendor2Calls;
        }else{
            return;
        }       
    
    
        key = service + "." + method;
    // It would be the code if I use a regular HashMap instead of ConcurrentHashMap
        /*Event event = map.get(key);       
    
        // Map does not contain this service.method, create an Event for the first     time.
        if(event == null){
            event = new Event();            
            map.put(key, event);
    
            // Map already contains this key, just adjust the numbers.
        }else{
            // Modify the object fields
        }*/
        //}
    
        // Make it thread-safe using CHM
        Event newEvent = new Event();
        Event existingEvent= map.putIfAbsent(key, newEvent); 
    
        if(existingEvent!=null && existingEvent!=newEvent){
            // Modify the object fields
    }       
    
    private void saveEntries(){
    
        Map<String, List<Event>> engineCalls = null;
        try {           
    
            engineCalls = new HashMap<String, List<Event>>();
            List<Event> events = null;
    
    // How can I achieve therad safety here w/o applying any lock?
            //synchronized(lock){
                if(!vendor1Calls.isEmpty()){
                    events = new ArrayList<Event>();
                    events.addAll(vendor1Calls.values());
                    engineCalls.put(vendor1, events);
                    vendor1Calls.clear();
                }
                if(!vendor2Calls.isEmpty()){
                    events = new ArrayList<Event>();
                    events.addAll(vendor2Calls.values());
                    engineCalls.put(vendor2, events);
                    vendor2Calls.clear();
                }
            //}
    
    // logICalls() saves the events in the DB.          
            DBHandle.logCalls(engineCalls);
        } catch (Throwable t) {         
        } finally {
            if(engineCalls!=null){
                engineCalls.clear();
            }                       
        }   
    }       
    

    }