0

The single-check idiom can be used to implement a thread safe-lazy init with (compared to the double-check) the possible drawback of wasting some computation time by multiple concurrent inits. It is

Single-check idiom

private volatile FieldType field;
FieldType getField() {
  FieldType result = field;
  if (result == null) {
    field = result = computeFieldValue();
  }
  return result;
}

Here, we need the volatile field to avoid that a partially initialized object is passed to another thread, that is - the assignment (write) implicitly performs the necessary synchronization.

I would like to implement a parametrized lazy init cache, which is essentially represented by a Map<Integer, Object>, where each element is created using lazy init.

My question is: is it enough to use a ConcurrentHashMap to avoid the issue with partial initialization. That is, in that case the thread-safe implementation of a lazy-init cache using the single-check idiom can be given by

private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>();

ItemType getItem(Integer index) {
  ItemType result = items.get(index);
  if (result == null) {
    result = computeItemValue(index);
    items.put(index, result);
  }
  return result;
}

In other words: I assume that 'items.put(index, result)' performs the necessary synchronization (since it is a write). Note that the question might be twofold here: first I wonder if this works in the (a) current JVM implementation, second (more importantly) I wonder if this is guaranteed (given the documentation/contract) of ConcurrentHashMap.

Note: Here I assume that computeItemValue generates an immutable object and guarantees thread safety in the sense of the single-check idiom (that is, once object construction is completed, the object returned behaves identically for all threads). I assume this is item 71 in J. Bloch's book.

Christian Fries
  • 16,175
  • 10
  • 56
  • 67
  • 2
    Neither of your suggestions is thread safe. Multiple threads can call the `computeFieldValue` and assign the value to the field/add it to the map. For `ConcurrentHashMap` use `putIfAbsent` or `computeIfAbsent`. – Sotirios Delimanolis Aug 11 '15 at 14:31
  • They are thread safe in the sense of the single-check idiom that computeFieldValue is constructing an immutable object which behaves the same, even if constructed multiple times... (please the the single check idiom in Bloch's book). – Christian Fries Aug 11 '15 at 14:33
  • @ChristianFries A correct singleton pattern doesn't initialize the instance more than once. – Kayaman Aug 11 '15 at 14:35
  • @Kayaman: In that case I would need to use the double-check idiom. I do know this, but it comes with additional syncronization and performance-wise it might be better to go for the single-check idiom. – Christian Fries Aug 11 '15 at 14:37

2 Answers2

4

In java 8, you can use the computeIfAbsent to avoid even the possibility of duplicate initialization:

private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>();

ItemType getItem(Integer index) {
  return items.computeIfAbsent(index, this::computeItemValue);
}
Brett Okken
  • 6,210
  • 1
  • 19
  • 25
1

is it enough to use a ConcurrentHashMap to avoid the issue with partial initialization.

Yes, there is an explicit happens-before ordering for objects read from a CHM relative to being written.

So, you cover visibility, but haven't touched on atomicity. There are two components on atomicity

  • memoization
  • put if not present

You aren't achieving either. That is, for memoization, you can create more than one object (not actually lazy-init). And for the put if not present, the CHM can receive more than one value since you are not invoking putIfAbsent.

Based on your last update, if they are going to be the same object and are immutable, than you should be fine despite extra construction.

John Vint
  • 39,695
  • 7
  • 78
  • 108
  • Thank you. What if we have the situation that we have two different immutable objects which behave indistinguishable (except that they might have two different addresses / id's)? (I will edit my question in that directions) – Christian Fries Aug 11 '15 at 14:43
  • @ChristianFries What do you mean by id? Like a sequence? In that case you are in trouble. If you can create two objects `ItemType a = computeItemValue(index)` and `ItemType b = computeItemValue(index)` with the same `index` such that `a.equals(b)` is false. This won't work. – John Vint Aug 11 '15 at 15:03
  • If you are using Java 8 and can leverage `computeIfAbsent` as suggested by Brett Okken, than definitely, definitely do that. – John Vint Aug 11 '15 at 15:04
  • By "id" I meant s.th. related to the memory address of the object - but the objects are considered equal in the sense of .equals. – Christian Fries Aug 13 '15 at 07:31