This discussion is archived
1 2 Previous Next 17 Replies Latest reply: Dec 8, 2011 9:04 AM by 456795 RSS

Withholding creation of value in using putIfAbsent() of ConcurrentHashMap

asrivast Newbie
Currently Being Moderated
Hi,
What would be the best and thread safe implementation if I use putIfAbsent() method of ConcurrentHashMap in such a way that the value I am putting is only created if the Map does not contain the key? Our Object (used as a Map Entry value) is expensive to create and if an entry is already present do not want to create a new one. Instead of using synchronizedMap and locking the entire Map I would like to take advantages of ConcurrentHashMap.

In the following sample I would like StringFactory.createNew() to be only called if the chm does not contain the "key" or else return the value it already has.

public class CHMTest {
@Test
public void testConcurrentHashMap() {
ConcurrentHashMap chm = new ConcurrentHashMap();
for (int i = 0; i < 3; i++) {
System.out.println(chm.putIfAbsent("key", StringFactory.createNew()));
}
}

private static class StringFactory {
public static String createNew() {
System.out.println("Creating a new String -- Very Very expensive");
return UUID.randomUUID().toString();
}
}
}
  • 1. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    EJP Guru
    Currently Being Moderated
    What would be the best and thread safe implementation if I use putIfAbsent() method of ConcurrentHashMap in such a way that the value I am putting is only created if the Map does not contain the key?
    Well that's what it does, and it is atomic. What's your question?
  • 2. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    asrivast Newbie
    Currently Being Moderated
    I don't want to call StringFactory.createNew if a key already exists but I want to do this without synchronizing on anything?

    synchronize (chm) {
    Object o = chm.get("key");
    if (o == null) {
    Object n = StringFactory.createNew();
    o = chm.putIfAbsent("key", n);
    if (o == null) {
    o = v;
    }
    }
    }

    This is logically what I want to do but without using explicit synchronizing()
  • 3. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    836548 Journeyer
    Currently Being Moderated
    asrivast wrote:
    I don't want to call StringFactory.createNew if a key already exists but I want to do this without synchronizing on anything?

    synchronize (chm) {
    Object o = chm.get("key");
    if (o == null) {
    Object n = StringFactory.createNew();
    o = chm.putIfAbsent("key", n);
    if (o == null) {
    o = v;
    }
    }
    }

    This is logically what I want to do but without using explicit synchronizing()
    Regarding ConcurrentHashMap, it is clearly mentioned in the java docs that This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.

    There is a difference in thread safety and Synchronization.

    But if you are planning to use putIfAbsent method, then i think it' ll works, because this method is inherited from ConcurrentMap interface, and they are atomic.
  • 4. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    EJP Guru
    Currently Being Moderated
    Please read the thread before you answer. That's not the issue. I don't think there is an answer, but yours certainly isn't one.
  • 5. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    836548 Journeyer
    Currently Being Moderated
    EJP wrote:
    Please read the thread before you answer. That's not the issue.
    I read the thread, and what i understood is OP wants to call StringFactory.createNew() method only if the key doesn't exists. Now to achieve this he don't want to use synchronize method or block and asking can he do it using ConcurrentHashMap's putIfAbsent method.
    I don't think there is an answer,
    Why you think there is no answer. throw light on it.
    but yours certainly isn't one.
    I made my point very clear, what java doc's says (Most of the times it's been assumed that Thread Safety and synchronization is same. so i pointed out) and as ConcurrentHashMap implements concurrentMap, and the implementation of putIfAbsent is atomic (means not sure, but OP can check it , by testing it in multi thread environment). Appreciate if you could reason it.


    @Op: As said earlier if it won't work in multithread environment, then instead of using the method synchronization you could use the block synchronization, just make the concurrentHashMap.putIfAbsent call synchronized.

    Edited by: 833545 on Oct 18, 2011 3:36 PM
  • 6. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    jtahlborn Expert
    Currently Being Moderated
    there are a number of options, depending on how strict you want to be on the "no extra work" idea. for instance, i commonly use this pattern for data where you will usually get a cache hit:
    Object result = map.get(key);
    if(result == null) {
      // ... create newValue ...
      map.putIfAbsent(key, newValue);
    }
    this avoids creating newValue if already in the map. additionally, unless you have high contention for the same key, then you will rarely do extra work (putIfAbsent will usually succeed).

    if you absolutely must not do extra work, then you'll need additional usage of synchronized/Lock. you have 2 options there (note you don't need putIfAbsent but you do need a second get() call after synchronizing):

    simple: use a single lock across all "create new" calls:
    Object result = map.get(key);
    if(result == null) {
      synchronized(factory) {
        if(map.get(key) == null) {
          // ... create newValue ...
          map.put(key, newValue);
        }
      }
    }
    complex: use a lock per key:
    Object result = map.get(key);
    if(result == null) {
      // ... get key specific lock ...
      synchronized(keyLock) {
        if(map.get(key) == null) {
          // ... create newValue ...
          map.put(key, newValue);
        }
      }
    }
  • 7. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    796440 Guru
    Currently Being Moderated
    I ususally do:
    if (!map.containsKey(x)) {
      map.putIfAbsent(x, new ExpensiveThingy());
    }
    It does require two tests (sort of a twist on DCL), but there's no explicit syncing necessary, and about as small performance hit as you can get.
  • 8. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    jtahlborn Expert
    Currently Being Moderated
    jverd wrote:
    I ususally do:
    if (!map.containsKey(x)) {
    map.putIfAbsent(x, new ExpensiveThingy());
    }
    It does require two tests (sort of a twist on DCL), but there's no explicit syncing necessary, and about as small performance hit as you can get.
    that's almost my first example (above), however, how do you actually get the value? if you do another get() after this, you're doing more work than you need to by using containsKey().
  • 9. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    796440 Guru
    Currently Being Moderated
    jtahlborn wrote:
    jverd wrote:
    I ususally do:
    if (!map.containsKey(x)) {
    map.putIfAbsent(x, new ExpensiveThingy());
    }
    It does require two tests (sort of a twist on DCL), but there's no explicit syncing necessary, and about as small performance hit as you can get.
    that's almost my first example (above),
    Yeah, I noticed that. :-) Just adding one more (albeit almost identical) option.
    however, how do you actually get the value? if you do another get() after this, you're doing more work than you need to by using containsKey().
    Right. I find this idiom:
    if (!map.containsKey(x)) {
      map.putIfAbsent(x, new ExpensiveThingy());
    }
    
    ExpensiveThingy et = map.get(x);
    easiest to write and read, so that's what I usually do. I doubt the extra get is significant in most cases, and it keeps the code cleaner than with the syncing needed for the get() approach.

    (I also have the habit of using containsKey() over get() == null because a) it describes exactly what I'm trying to do and b) get() returning null can mean the key is present with a null value--even though this doesn't happen in any map I can ever recall using, and is not even allowed by ConcurrentHashMap as far as I know. Just a habit.

    EDIT:

    To expand a bit. With your first example:
    Object result = map.get(key);
    if(result == null) {
      // ... create newValue ...
      map.putIfAbsent(key, newValue);
    }
    We have to either sync or call get again. We can't use newValue, because we don't know that another thread hasn't come in between our null test and our put() call and put() its own entry. Using get() doesn't save anything over containsKey(), if I'm not missing something.

    Edited by: jverd on Oct 18, 2011 10:15 AM
  • 10. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    EJP Guru
    Currently Being Moderated
    I read the thread, and what i understood is OP wants to call StringFactory.createNew() method only if the key doesn't exists. Now to achieve this he don't want to use synchronize method or block and asking can he do it using ConcurrentHashMap's putIfAbsent method.
    And you said 'it works' for his objective when it doesn't. It calls the expensive method whether or not the key is inserted.
    Why you think there is no answer. throw light on it.
    Because he needs to determine whether the key is present, and call the expensive operation and insert the result if it isn't, which is two operations on the map, which is not atomic, so he would need to synchronize.
    I made my point very clear, what java doc's says .. Appreciate if you could reason it.
    See above. putIfAbsent() Is atomic all right, but by itself it is not sufficient to solve the problem.
    @Op: As said earlier if it won't work in multithread environment, then instead of using the method synchronization you could use the block synchronization, just make the concurrentHashMap.putIfAbsent call synchronized.
    Pointless. putIfAbsent() is atomic. Synchronizing it won't add anything. And it's still not a solution, because putIfAbsent() by itself isn't a solution. And he has already said he doesn't want to lock the entire map. So here again you prove my point. Either you haven't read the thread or at best you don't understand it.
  • 11. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    796440 Guru
    Currently Being Moderated
    @OP: The only other options I see are to change your class so that it's cheap to create, but then does the expensive stuff the first time you use it, or provide a decorator that just holds the c'tor params and then creates the object to delegate to on first call that actually does anything with it.
  • 12. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    EJP Guru
    Currently Being Moderated
    He could override the get() method to lazily instantiate.
  • 13. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    796440 Guru
    Currently Being Moderated
    EJP wrote:
    He could override the get() method to lazily instantiate.
    Yeah, that and any map methods that return the object. As long as he doesn't keep a separate local reference to the new object after put()ing it.
  • 14. Re: Withholding creation of value in using putIfAbsent() of ConcurrentHashMap
    jtahlborn Expert
    Currently Being Moderated
    jverd wrote:
    To expand a bit. With your first example:
    Object result = map.get(key);
    if(result == null) {
    // ... create newValue ...
    map.putIfAbsent(key, newValue);
    }
    We have to either sync or call get again. We can't use newValue, because we don't know that another thread hasn't come in between our null test and our put() call and put() its own entry. Using get() doesn't save anything over containsKey(), if I'm not missing something.
    technically, you are missing something, as i didn't write out the full example. the "complete" code would be:
    Object result = map.get(key);
    if(result == null) {
      // ... create newValue ...
      Object oldResult = map.putIfAbsent(key, newValue);
      if(oldResult != null) {
        result = oldResult;
      } else {
        result  = newValue;
      }
    }
    this leaves you with 1 lookup in the cache hit scenario, 2 in the cache miss scenario. but (as far as the OP is concerned), this solution only narrows the timing hole for doing the "expensive work", does not close it completely.
1 2 Previous Next

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points