(Quick Reference)

8 STM - Reference Documentation

Authors: The Whole GPars Gang

Version: 1.0.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 site

Running 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.IntRef
import static org.multiverse.api.StmUtils.newIntRef

public class Account { private final IntRef amount = newIntRef(0);

public void transfer(final int a) { GParsStm.atomic { amount.increment(a); } }

public int getCurrentAmount() { GParsStm.atomicWithInt { amount.get(); } } }

There are several types of atomic closures, each for different type of return value:

  • atomic - returning Object
  • atomicWithInt - returning int
  • atomicWithLong - returning long
  • atomicWithBoolean - returning boolean
  • atomicWithDouble - returning double
  • atomicWithVoid - no return value

Multiverse by default uses optimistic locking strategy and automatically rolls back and retries colliding transactions. Developers should thus restrain from irreversible actions (e.g. writing to the console, sending and e-mail, launching a missile, etc.) in their transactional code. To increase flexibility, the default Multiverse settings can be customized through custom atomic blocks .

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

final AtomicBlock block = GParsStm.createAtomicBlock(maxRetries: 3000, familyName: 'Custom', PropagationLevel: PropagationLevel.Requires, interruptible: false) assert GParsStm.atomicWithBoolean(block) { true }

The customized AtomicBlock can then be used to create transactions following the specified settings. AtomicBlock instances are thread-safe and can be freely reused among threads and transactions.

Using the Transaction object

The atomic closures are provided the current Transaction as a parameter. The Transaction objects 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.AtomicBlock
import org.multiverse.api.PropagationLevel
import static org.multiverse.api.StmUtils.newIntRef

final AtomicBlock block = GParsStm.createAtomicBlock(maxRetries: 3000, familyName: 'Custom', PropagationLevel: PropagationLevel.Requires, interruptible: false)

def counter = newIntRef(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:

  • IntRef
  • LongRef
  • BooleanRef
  • DoubleRef
  • Ref

You typically create these through the factory methods of the org.multiverse.api.StmUtils class.

More information

We decided not to duplicate the information that is already available on the Multiverse website. Please visit the Multiverse site and use it as a reference for your further Stm adventures with GPars.