JBoss.orgCommunity Documentation
JBoss Cache is a thread safe caching API, and uses its own efficient mechanisms of controlling concurrent access. It uses an innovative implementation of multi-versioned concurrency control (MVCC) as the default locking scheme. Versions of JBoss Cache prior to 3.x offered Optimistic and Pessimistic Locking schemes, both of which are now deprecated in favor of MVCC.
MVCC is a locking scheme commonly used by modern database implementations to control fast, safe concurrent access to shared data.
MVCC is designed to provide the following features for concurrent access:
and achieves this by using data versioning and copying for concurrent writers. The theory is that readers continue reading shared state, while writers copy the shared state, increment a version id, and write that shared state back after verifying that the version is still valid (i.e., another concurrent writer has not changed this state first).
This allows readers to continue reading while not preventing writers from writing, and repeatable read semantics are maintained by allowing readers to read off the old version of the state.
JBoss Cache's implementation of MVCC is based on a few features:
The extremely high performance of JBoss Cache's MVCC implementation for reading threads is achieved by
not requiring any synchronization or locking for readers. For each reader thread, the
MVCCLockingInterceptor
wraps state in a lightweight container object, which is placed
in the thread's InvocationContext
(or TransactionContext
if running
in a transaction). All subsequent operations on the state happens via the container object. This use of
Java references allows for repeatable read semantics even if the actual state changes simultaneously.
Writer threads, on the other hand, need to acquire a lock before any writing can commence. Currently,
we use lock striping to improve the memory performance of the cache, and the size of the shared lock pool
can be tuned using the concurrencyLevel
attribute of the locking
element. See the configuration reference
for details. After acquiring an exclusive lock on an Fqn, the writer thread then wraps the state to be
modified in a container as well, just like with reader threads, and then copies this state for writing.
When copying, a reference to the original version is still maintained in the container (for rollbacks).
Changes are then made to the copy and the copy is finally written to the data structure when the write
completes.
This way, subsequent readers see the new version while existing readers still hold a reference to the original version in their context.
If a writer is unable to acquire the write lock after some time, a TimeoutException
is
thrown. This lock acquisition timeout defaults to 10000 millis and can be configured using the
lockAcquisitionTimeout
attribute of the locking
element. See the
configuration reference for details.
JBoss Cache 3.x supports two isolation levels: REPEATABLE_READ and READ_COMMITTED, which correspond in semantic to database-style isolation levels. Previous versions of JBoss Cache supported all 5 database isolation levels, and if an unsupported isolation level is configured, it is either upgraded or downgraded to the closest supported level.
REPEATABLE_READ is the default isolation level, to maintain compatibility with previous versions of JBoss Cache. READ_COMMITTED, while providing a slightly weaker isolation, has a significant performance benefit over REPEATABLE_READ.
Although MVCC forces writers to obtain a write lock, a phenomenon known as write skews may occur when using REPEATABLE_READ:
This happens when concurrent transactions performing a read and then a write, based on the value that was read. Since reads involve holding on to the reference to the state in the transaction context, a subsequent write would work off that original state read, which may now be stale.
The default behavior with dealing with a write skew is to throw a DataVersioningException
,
when it is detected when copying state for writing. However, in most applications, a write skew may not
be an issue (for example, if the state written has no relationship to the state originally read) and
should be allowed. If your application does not care about write skews, you can allow them to happen
by setting the writeSkewCheck
configuration attribute to false
.
See the configuration reference for details.
Note that write skews cannot happen when using READ_COMMITTED since threads always work off committed state.
Configuring MVCC involves using the <locking />
configuration tag, as follows:
<locking
isolationLevel="REPEATABLE_READ"
lockAcquisitionTimeout="10234"
nodeLockingScheme="mvcc"
writeSkewCheck="false"
concurrencyLevel="1000" />
nodeLockingScheme
- the node locking scheme used. Defaults to MVCC if
not provided, deprecated schemes such as pessimistic
or optimistic
may be used but is not encouraged.isolationLevel
- transaction isolation level. Defaults to REPEATABLE_READ if not provided.writeSkewCheck
- defaults to true
if not provided.concurrencyLevel
- defaults to 500 if not provided.lockAcquisitionTimeout
- only applies to writers when using MVCC. Defaults to 10000 if not provided.
From JBoss Cache 3.x onwards, pessimistic and optimistic locking schemes are deprecated in favor of MVCC. It is recommended that existing applications move off these legacy locking schemes as support for them will eventually be dropped altogether in future releases.
Documentation for legacy locking schemes are not included in this user guide, and if necessary, can be referenced in previous versions of this document, which can be found on the JBoss Cache website.
JBoss Cache can be configured to use and participate in JTA compliant transactions. Alternatively, if transaction support is disabled, it is equivalent to using autocommit in JDBC calls, where modifications are potentially replicated after every change (if replication is enabled).
What JBoss Cache does on every incoming call is:
Retrieve the current
javax.transaction.Transaction
associated with the thread
If not already done, register a
javax.transaction.Synchronization
with the transaction manager to be notified when a transaction commits
or is rolled back.
In order to do this, the cache has to be provided with a reference to environment's
javax.transaction.TransactionManager
. This is usually done by configuring the cache
with the class name of an implementation of the TransactionManagerLookup
interface. When the cache starts, it will create an instance of this class and invoke its
getTransactionManager()
method, which returns a reference to the
TransactionManager
.
JBoss Cache ships with
JBossTransactionManagerLookup
and GenericTransactionManagerLookup
. The
JBossTransactionManagerLookup
is able to bind to a running JBoss AS instance and retrieve a
TransactionManager
while the GenericTransactionManagerLookup
is able to bind to most popular Java EE application servers and provide the same functionality. A dummy
implementation - DummyTransactionManagerLookup
- is also provided for unit tests. Being a
dummy, this is not recommended for production use a it has some severe limitations to do with concurrent
transactions and recovery.
An alternative to configuring a TransactionManagerLookup
is to programatically inject a
reference to the TransactionManager
into the Configuration
object's
RuntimeConfig
element:
TransactionManager tm = getTransactionManager(); // magic method
cache.getConfiguration().getRuntimeConfig().setTransactionManager(tm);
Injecting the TransactionManager
is the recommended approach when the
Configuration
is built by some sort of IOC container that already has a reference to the
TransactionManager
.
When the transaction commits, we initiate either a one- two-phase commit protocol. See replicated caches and transactions for details.