K
- the hash key typepublic interface HashQueryContext<K> extends HashContext<K>, SegmentLock
ChronicleHash
operations with individual keys.
This context provides access to InterProcessReadWriteUpdateLock
, governing access
to the entry. Your have no chance to perform any actions under race with other threads, because
all required locks are acquired automatically on each operation, you should deal with locks
manually in the following cases:
IllegalMonitorStateException
, because this is
dead lock prone. (See InterProcessReadWriteUpdateLock
documentation for explanation.
So, such context code is incorrect:
// INCORRECT
try (ExternalMapQueryContext<K, V, ?> q = map.queryContext(key)) {
// q.entry(), checks if the entry is present in the map, and acquires
// the read lock for that.
MapEntry<K, V> entry = q.entry();
if (entry != null) {
// Tries to acquire the write lock to perform modification,
// but this is an illegal upgrade: read -> write, throws IllegalMonitorStateException
q.remove(entry);
}
}
So, to workaround this, you should acquire the update lock,
which is upgradable to the write lock, before performing any reading in the context:
// CORRECT
try (ExternalMapQueryContext<K, V, ?> q = map.queryContext(key)) {
q.updateLock().lock(); // acquire the update lock before checking the entry presence.
MapEntry<K, V> entry = q.entry();
if (entry != null)
q.remove(entry);
}
ChronicleHash
concurrency. You should base on probabilities of making some reading or
writing operations. For example, see how MapMethods.acquireUsing(net.openhft.chronicle.map.MapQueryContext<K, V, R>, net.openhft.chronicle.map.ReturnValue<V>)
might be
implemented:
default void acquireUsing(MapQueryContext<K, V, R> q, ReturnValue<V> returnValue) {
// For acquireUsing(), it is assumed to be very probable, that the entry is already
// present in the map, so we will perform the whole acquireUsing() without exclusive locking
if (q.readLock().tryLock()) {
MapEntry<K, V> entry = q.entry();
if (entry != null) {
// Entry is present, return
returnValue.returnValue(entry.value());
return;
}
// Key is absent
// Need to unlock, to lock to update lock later. Direct upgrade is forbidden.
q.readLock().unlock();
}
// We are here, either if we:
// 1) Failed to acquire the read lock, this means some other thread is holding the write
// lock now, in this case waiting for the update lock acquisition is no longer, than for
// the read
// 2) Seen the entry is absent under the read lock. This means we need to insert
// the default value into the map. that requires update-level access as well
q.updateLock().lock();
MapEntry<K, V> entry = q.entry();
if (entry != null) {
// Entry is present, return
returnValue.returnValue(entry.value());
return;
}
// Key is absent
q.insert(q.absentEntry(), q.defaultValue(q.absentEntry()));
returnValue.returnValue(q.entry().value());
}
InterProcessLock.lock()
policy of trying to acquire the
lock for some time, and then throw RuntimeException
, or need a custom timeout:
try (ExternalHashQueryContext<K> q = hash.queryContext(key)) {
if (q.writeLock().tryLock(5, TimeUnit.SECONDS)) {
// do something
} else {
// do something else, maybe not throwing an exception
}
}
HashQueryContext
defines the common pattern for working with ChronicleHash
contexts: it has a pair of methods, entry()
and absentEntry()
, at any moment
one of them returns an (absent) entry context object, another - null
, depending on the
presence of the queried key in the ChronicleHash
. Thus,
block of code that uses HashQueryContext
usually has an if-else statement,
with "then" branch for dealing with the present entry, else
branch for dealing with
the absent entry, or vise-versa. For example:
interface Point {
double getX();
void setX(double x);
double addX(double xAdd);
double getY();
void setY(double y);
double addY(double yAdd);
}
<K> Point movePoint(ChronicleMap<K, Point> map, K key, double xMove, double yMove,
Point using) {
// Moves existing point by [xMove, yMove], if absent - assumes the default point is [0, 0].
// Returns the resulting point
try (ExternalMapQueryContext<K, Point, ?> q = map.queryContext(key)) {
Point offHeapPoint;
q.updateLock().lock();
MapEntry<K, Point> entry = q.entry();
if (entry != null) {
// Key is present
offHeapPoint = entry.value().getUsing(using);
} else {
// Key is absent
q.insert(q.absentEntry(), q.defaultValue(q.absentEntry()));
offHeapPoint = q.entry().value().getUsing(using);
}
offHeapPoint.addX(xMove);
offHeapPoint.addY(yMove);
return offHeapPoint;
}
}
HashQueryContext
is the base interface defining the structure, but it has no methods
to anything "interesting" with entry()
or absentEntry()
. Use MapQueryContext
or SetQueryContext
interfaces, which provide access to MapEntryOperations
and SetEntryOperations
respectively.
ChronicleHash.queryContext(Object)
Modifier and Type | Method and Description |
---|---|
HashAbsentEntry<K> |
absentEntry()
Returns the special absent entry object, if the entry with the queried key
is absent in the hash, returns
null , if the entry is present. |
HashEntry<K> |
entry()
Returns the entry context, if the entry with the queried key is present
in the
ChronicleHash , returns null is the entry is absent. |
Data<K> |
queriedKey()
Returns the queried key as a
Data . |
int |
segmentIndex()
Returns the index of the accessed segment, where the queried key is located (or to which
the key is going to be put).
|
hash
readLock, updateLock, writeLock
int segmentIndex()
This index might also be used as the InterProcessReadWriteUpdateLock
identifier,
because ChronicleHashes
has per-segment locks.
segmentIndex
in interface SegmentLock
HashEntry<K> entry()
ChronicleHash
, returns null
is the entry is absent.InterProcessReadWriteUpdateLock.readLock()
before searching for the key, if the context
is not locked yet.@Nullable HashAbsentEntry<K> absentEntry()
null
, if the entry is present.InterProcessReadWriteUpdateLock.readLock()
before searching for the key, if the context
is not locked yet.Copyright © 2022. All rights reserved.