JBoss.orgCommunity Documentation

Chapter 11. Transactions and Concurrency

11.1. Concurrent Access
11.1.1. Multi-Version Concurrency Control (MVCC)
11.1.2. Pessimistic and Optimistic Locking Schemes
11.2. JTA Support

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.

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.

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:

  1. Retrieve the current javax.transaction.Transaction associated with the thread

  2. 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.