Concurrent, distributed, observable and queryable map.
This class is not a general-purpose
ConcurrentMap implementation! While this class implements
the
Map interface, it intentionally violates
Map's general contract, which mandates the
use of the
equals method when comparing objects. Instead of the
equals method, this
implementation compares the serialized byte version of the objects.
Moreover, stored values are handled as having a value type semantics, while standard Java implementations treat them
as having a reference type semantics.
Gotchas:
- Methods, including but not limited to
get,
containsKey,
containsValue,
evict,
remove,
put,
putIfAbsent,
replace,
lock,
unlock, do not use
hashCode and
equals implementations of keys. Instead, they use
hashCode and
equalsof binary (serialized) forms of the objects.
- The
get method returns a clone of original values, so modifying the returned value does not change
the actual value in the map. You should put the modified value back to make changes visible to all nodes.
For additional info, see
IMap#get(Object).
- Methods, including but not limited to
keySet,
values,
entrySet, return a collection
clone of the values. The collection is NOT backed by the map, so changes to the map are NOT reflected
in the collection, and vice-versa.
- Since Hazelcast is compiled with Java 1.6, we can't override default methods introduced in later Java versions,
nor can we add documentation to them. Methods, including but not limited to
computeIfPresent, may behave
incorrectly if the value passed to the update function is modified in-place and returned as a result of the invocation.
You should create a new value instance and return it as a result.
For example, following code fragment will behave incorrectly and will enter an infinite loop:
map.computeIfPresent("key", (key, value) -> {
value.setSomeAttribute("newAttributeValue");
return value;
});
It should be replaced with:
map.computeIfPresent("key", (key, value) -> {
return new ObjectWithSomeAttribute("newAttributeValue");
});
- Be careful while using default interface method implementations from
ConcurrentMap and
Map. Under
the hood they are typically implemented as a sequence of more primitive map operations, therefore the operations won't
be executed atomically.
This class does not allow
null to be used as a key or value.
Entry Processing
The following operations are lock-aware, since they operate on a single key only.
If the key is locked, the EntryProcessor will wait until it acquires the lock.
-
IMap#executeOnKey(Object,EntryProcessor)
-
IMap#submitToKey(Object,EntryProcessor)
-
IMap#submitToKey(Object,EntryProcessor,ExecutionCallback)
However, there are following methods that run the
EntryProcessor on more than one entry.
These operations are not lock-aware.
The
EntryProcessor will process the entries no matter if they are locked or not.
The user may however check if an entry is locked by casting the
java.util.Map.Entry to
LockAware and invoking the
LockAware#isLocked() method.
-
IMap#executeOnEntries(EntryProcessor)
-
IMap#executeOnEntries(EntryProcessor,Predicate)
-
IMap#executeOnKeys(Set,EntryProcessor)
This applies to both
EntryProcessor and
BackupEntryProcessor.
Split-brain
Behaviour of
IMap under split-brain scenarios should be taken into account when using this
data structure. During a split, each partitioned cluster will either create a brand new
IMapor it will continue to use the primary or back-up version.
When the split heals, Hazelcast by default, performs a
com.hazelcast.map.merge.PutIfAbsentMapMergePolicy.
Users can also decide to
specify their own map merge policies, these policies when used in concert with
CRDTs (Convergent and Commutative
Replicated Data Types) can ensure against data loss during a split-brain.
As a defensive mechanism against such inconsistency, consider using the in-built
split-brain protection for
IMap. Using this functionality it is possible to restrict operations in smaller
partitioned clusters. It should be noted that there is still an inconsistency window between the time of
the split and the actual detection. Therefore using this reduces the window of inconsistency but can never
completely eliminate it.
Interactions with the map store
Maps can be configured to be backed by a map store to persist the
entries. In this case many of the IMap methods call
MapLoader or
MapStore methods to load, store or remove data. Each method's
javadoc describes the way of its interaction with the map store.
Expiration and eviction
Expiration puts a limit on the maximum lifetime of an entry stored inside the map. When the entry expires
it can't be retrieved from the map any longer and at some point in time it will be cleaned out from the map
to free up the memory. There are two expiration policies:
- The time-to-live (TTL) expiration policy limits the lifetime of the entry relative to the time of the last
write access performed on the entry. The default TTL value for the map may be configured using the
time-to-live-seconds setting, which has an infinite by default. An individual entry may have its own TTL
value assigned using one of the methods accepting a TTL value, for instance using the
#put(Object,Object,long,TimeUnit) method. If there is no TTL value provided for the individual
entry, it inherits the value set in the map configuration.
- The max-idle expiration policy limits the lifetime of the entry relative to the time of the last read or
write access performed on the entry. The max-idle value for the map may be configured using the
max-idle-seconds setting, which has an infinite value by default.
Both expiration policies may be used simultaneously on the map entries. In such case, the entry is considered expired
if at least one of the policies marks it as expired.
Eviction puts a limit on the maximum size of the map. If the size of the map grows larger than the maximum allowed
size, an eviction policy decides which item to evict from the map to reduce its size. The maximum allowed size may
be configured using the
com.hazelcast.config.MaxSizeConfig.MaxSizePolicy setting and the eviction
policy may be configured using the
com.hazelcast.config.EvictionPolicy setting as well.
By default, maps have no restrictions on the size and may grow arbitrarily large.
Eviction may be enabled along with the expiration policies. In such case, the expiration policies continue to work
as usual cleaning out the expired entries regardless of the map size.
Locked map entries are not the subjects for the expiration and eviction policies.
Mutating methods without TTL
Certain
IMap methods perform the entry set mutation and don't accept TTL as a parameter. Entries
created or updated by such methods are subjects for the following TTL calculation procedure:
- If the entry is new, i.e. the entry was created, it receives the default TTL value configured for
the map using the
time-to-live-seconds configuration setting. If this setting is not provided for
the map, the entry receives an infinite TTL value.
- If the entry already exists, i.e. the entry was updated, its TTL value remains unchanged and its
lifetime is prolonged by this TTL value.
The methods to which this procedure applies:
#put(Object,Object),
#set(Object,Object),
#putAsync(Object,Object),
#setAsync(Object,Object),
#tryPut(Object,Object,long,TimeUnit),
#putAll(Map),
#replace(Object,Object,Object) and
#replace(Object,Object).