// need 'synchronized' to ensure atomic initialization of merged data // because several threads that acquired read lock may simultaneously execute the method private ValueContainerImpl<Value> getMergedData() { ValueContainerImpl<Value> merged = myMerged; if (merged != null) { return merged; } synchronized (myInitializer.getLock()) { merged = myMerged; if (merged != null) { return merged; } final ValueContainer<Value> fromDisk = myInitializer.compute(); final ValueContainerImpl<Value> newMerged; if (fromDisk instanceof ValueContainerImpl) { newMerged = ((ValueContainerImpl<Value>) fromDisk).copy(); } else { newMerged = ((ChangeTrackingValueContainer<Value>) fromDisk).getMergedData().copy(); } TIntHashSet invalidated = myInvalidated; if (invalidated != null) { invalidated.forEach( new TIntProcedure() { @Override public boolean execute(int inputId) { newMerged.removeAssociatedValue(inputId); return true; } }); } ValueContainerImpl<Value> added = myAdded; if (added != null) { added.forEach( new ContainerAction<Value>() { @Override public boolean perform(final int id, final Value value) { newMerged.removeAssociatedValue( id); // enforcing "one-value-per-file for particular key" invariant newMerged.addValue(id, value); return true; } }); } setNeedsCompacting(fromDisk.needsCompacting()); myMerged = newMerged; return newMerged; } }