8 STM - Reference Documentation
Authors: The Whole GPars Gang
Version: 1.1.0
8 STM
Software Transactional Memory (STM) gives developers transactional semantics for accessing in-memory data. When multiple threads share data in memory, by marking blocks of code as transactional (atomic) the developer delegates the responsibility for data consistency to the Stm engine. GPars leverages the Multiverse Stm engine. Check out more details on the transactional engine at the Multiverse siteRunning a piece of code atomically
When using Stm, developers organize their code into transactions. A transaction is a piece of code, which is executed atomically - either all the code is run or none at all. The data used by the transactional code remains consistent irrespective of whether the transaction finishes normally or abruptly. While running inside a transaction the code is given an illusion of being isolated from the other concurrently run transactions so that changes to data in one transaction are not visible in the other ones until the transactions commit. This gives us the ACI part of the ACID characteristics of database transactions. The durability transactional aspect so typical for databases, is not typically mandated for Stm.GPars allows developers to specify transaction boundaries by using the atomic closures.import groovyx.gpars.stm.GParsStm import org.multiverse.api.references.TxnInteger import static org.multiverse.api.StmUtils.newTxnIntegerpublic class Account { private final TxnInteger amount = newTxnInteger(0); public void transfer(final int a) { GParsStm.atomic { amount.increment(a); } } public int getCurrentAmount() { GParsStm.atomicWithInt { amount.get(); } } }
- atomic - returning Object
- atomicWithInt - returning int
- atomicWithLong - returning long
- atomicWithBoolean - returning boolean
- atomicWithDouble - returning double
- atomicWithVoid - no return value
Customizing the transactional properties
Frequently it may be desired to specify different values for some of the transaction properties (e.g. read-only transactions, locking strategy, isolation level, etc.). The createAtomicBlock method will create a new AtomicBlock configured with the supplied values:import groovyx.gpars.stm.GParsStm import org.multiverse.api.AtomicBlock import org.multiverse.api.PropagationLevelfinal TxnExecutor block = GParsStm.createTxnExecutor(maxRetries: 3000, familyName: 'Custom', PropagationLevel: PropagationLevel.Requires, interruptible: false) assert GParsStm.atomicWithBoolean(block) { true }
Using the Transaction object
The atomic closures are provided the current Transaction as a parameter. The Txn objects representing a transaction can then be used to manually control the transaction. This is illustrated in the example below, where we use the retry() method to block the current transaction until the counter reaches the desired value:import groovyx.gpars.stm.GParsStm import org.multiverse.api.PropagationLevel import org.multiverse.api.TxnExecutorimport static org.multiverse.api.StmUtils.newTxnIntegerfinal TxnExecutor block = GParsStm.createTxnExecutor(maxRetries: 3000, familyName: 'Custom', PropagationLevel: PropagationLevel.Requires, interruptible: false)def counter = newTxnInteger(0) final int max = 100 Thread.start { while (counter.atomicGet() < max) { counter.atomicIncrementAndGet(1) sleep 10 } } assert max + 1 == GParsStm.atomicWithInt(block) { tx -> if (counter.get() == max) return counter.get() + 1 tx.retry() }
Data structures
You might have noticed in the code examples above that we use dedicated data structures to hold values. The fact is that normal Java classes do not support transactions and thus cannot be used directly, since Multiverse would not be able to share them safely among concurrent transactions, commit them nor roll them back. We need to use data that know about transactions:- TxnIntRef
- TxnLongRef
- TxnBooleanRef
- TxnDoubleRef
- TxnRef