├── .gitignore ├── README.markdown ├── src └── concurrency │ ├── stm │ ├── Context.java │ ├── GlobalContext.java │ ├── Ref.java │ ├── RefTuple.java │ ├── STM.java │ ├── Transaction.java │ └── TransactionBlock.java │ └── test │ ├── Account.java │ ├── Bank.java │ ├── BankThread.java │ ├── NonSyncStrategy.java │ ├── RefTest.java │ ├── Runner.java │ ├── STMStrategy.java │ ├── SyncStrategy.java │ └── TransferStrategy.java └── stm-java.iml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Binaries 2 | *.class 3 | *.jar 4 | out/ 5 | 6 | # Idea files 7 | *.ipr 8 | *.iws 9 | .idea/ 10 | 11 | # Emacs files 12 | *~ 13 | .DS_Store 14 | *~ 15 | *# 16 | .#* -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Implementing STM 2 | 3 | ## Getting Started 4 | 5 | 1. fork this repository 6 | 2. clone your fork 7 | 3. switch to branch 0.1 8 | 9 | `git checkout tags/0.1` 10 | 11 | STM related functionality located under `concurrency.stm` package. 12 | `concurrency.test` is primarily for testing purposes. 13 | 14 | You can use slides from the [Presentation](http://www.slideshare.net/mishadoff/implementing-stm-in-java) as a source. 15 | 16 | ## Tasks 17 | 18 | ### 1. Transaction return value 19 | 20 | We use `void` as a transaction return type. 21 | User must be able to do something like this: 22 | 23 | ``` java 24 | Long id = STM.transaction(...); 25 | String s = STM.transaction(...); 26 | ``` 27 | 28 | Note: You can change the API and add generic parameter to infer the actual return type. 29 | 30 | ### 2. Avoid calling `this.getTx()` for `get/set` ref methods. 31 | 32 | Not sure if it is possible due to Java limitations but 33 | investigate this problem, at least simplify access to the transaction. 34 | 35 | Note: One solution is to make `Transaction` protected inside `TransactionBlock` so, all callers, will obtain this reference by accessing `tx` variable. But I hope there is more elegant solution. Don't hesitate to change the internal API. 36 | 37 | ### 3. Fair `GlobalContext` 38 | 39 | `GlobalContext` just delegates `get` ref value to the actual ref value, and does not support mapping, like `Transaction`'s `inTxMap`. Do the same. 40 | 41 | Note: Your implementation need to register ref values, perform safe update of the mapping and cleanup, if ref is not accessible. 42 | 43 | ### 4. Snapshot Isolation 44 | 45 | In `Transaction.get()` we've cheated a bit. Our snapshot is *lazy*, it retrieves ref's value *on demand*. Implement the fair **Snapshot Isolation** 46 | 47 | Note: This task is slightly related to the previous one, as you need all ref values to build the snapshot. Perform defensive copy of snapshot as you need identity, not refs. 48 | 49 | ### 5. Ref history 50 | 51 | Current implementation contains one last version of ref value. Implement true *Multi-Versioning*. Suggest or implement a prototype, how multi-versioning can improve STM performance. 52 | 53 | ### 6. Exceptions 54 | 55 | We do not support exceptions, would be good to have. 56 | Basically, there are must be two types of exceptions. One should trigger transaction retry (it can be some internal reason to throw this exception), another one should be thrown up to the caller method. 57 | 58 | ### 7. Nested transactions 59 | 60 | I hope nested transactions are not supported in this code. Add them. 61 | Possible, we need to retry the whole outer transaction if collision detected inside nested transactions. 62 | 63 | ### 8. Instrumentation 64 | 65 | Provide set of tools to test STM performance and calculate some metrics. Number of transactions per second, number of retries, longest transaction, etc. Select at least 10 metrics, which ones is up to you. 66 | 67 | ### 9. Improvement 68 | 69 | Detect bottlenecks of current implementation and improve performance. 70 | 71 | Hint: `STM.commitLock` can be improved, but not limited to. 72 | 73 | ### 10. Implementation from scratch [HARD] 74 | 75 | Implement any STM algorithm. 76 | 77 | ___ 78 | 79 | Inspired by [STM in Scala](http://www.codecommit.com/blog/scala/software-transactional-memory-in-scala) 80 | -------------------------------------------------------------------------------- /src/concurrency/stm/Context.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public abstract class Context { 7 | abstract T get(Ref ref); 8 | } 9 | -------------------------------------------------------------------------------- /src/concurrency/stm/GlobalContext.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author mishadoff 8 | */ 9 | public final class GlobalContext extends Context { 10 | private static GlobalContext instance = new GlobalContext(); 11 | 12 | private GlobalContext() { } 13 | 14 | public static GlobalContext get() { 15 | return instance; 16 | } 17 | 18 | @Override 19 | T get(Ref ref) { 20 | return ref.content.value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/concurrency/stm/Ref.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public final class Ref { 7 | RefTuple content; 8 | 9 | public Ref(T value) { 10 | content = RefTuple.get(value, 0L); 11 | } 12 | 13 | public T getValue(Context ctx) { 14 | return ctx.get(this); 15 | } 16 | 17 | public void setValue(T value, Transaction tx) { 18 | tx.set(this, value); 19 | } 20 | 21 | /* 22 | UNSAFE 23 | ONLY FOR INSTRUMENTATION 24 | */ 25 | 26 | @Deprecated 27 | public T get() { 28 | return content.value; 29 | } 30 | 31 | @Deprecated 32 | public void set(T value) { 33 | this.content.value = value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/concurrency/stm/RefTuple.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public class RefTuple { 7 | V value; 8 | R revision; 9 | 10 | public RefTuple(V v, R r) { 11 | this.value = v; 12 | this.revision = r; 13 | } 14 | 15 | static RefTuple get(V v, R r) { 16 | return new RefTuple(v, r); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/concurrency/stm/STM.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public final class STM { 7 | private STM() {} 8 | 9 | public static final Object commitLock = new Object(); 10 | 11 | public static void transaction(TransactionBlock block) { 12 | boolean committed = false; 13 | while (!committed) { 14 | Transaction tx = new Transaction(); 15 | block.setTx(tx); 16 | block.run(); 17 | committed = tx.commit(); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/concurrency/stm/Transaction.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * @author mishadoff 11 | */ 12 | public final class Transaction extends Context{ 13 | private HashMap inTxMap = new HashMap<>(); 14 | private HashSet toUpdate = new HashSet<>(); 15 | private HashMap version = new HashMap<>(); 16 | 17 | private long revision; 18 | private static AtomicLong transactionNum = new AtomicLong(0); 19 | 20 | Transaction() { 21 | revision = transactionNum.incrementAndGet(); 22 | } 23 | 24 | @Override 25 | T get(Ref ref) { 26 | if (!inTxMap.containsKey(ref)) { 27 | RefTuple tuple = ref.content; 28 | inTxMap.put(ref, tuple.value); 29 | if (!version.containsKey(ref)) { 30 | version.put(ref, tuple.revision); 31 | } 32 | } 33 | return (T)inTxMap.get(ref); 34 | } 35 | 36 | void set(Ref ref, T value) { 37 | inTxMap.put(ref, value); 38 | toUpdate.add(ref); 39 | if (!version.containsKey(ref)) { 40 | version.put(ref, ref.content.revision); 41 | } 42 | } 43 | 44 | boolean commit() { 45 | synchronized (STM.commitLock) { 46 | // validation 47 | boolean isValid = true; 48 | for (Ref ref : inTxMap.keySet()) { 49 | if (ref.content.revision != version.get(ref)) { 50 | isValid = false; 51 | break; 52 | } 53 | } 54 | 55 | // writes 56 | if (isValid) { 57 | for (Ref ref : toUpdate) { 58 | ref.content = RefTuple.get(inTxMap.get(ref), revision); 59 | } 60 | } 61 | return isValid; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/concurrency/stm/TransactionBlock.java: -------------------------------------------------------------------------------- 1 | package concurrency.stm; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public abstract class TransactionBlock implements Runnable { 7 | private Transaction tx; 8 | 9 | void setTx(Transaction tx) { 10 | this.tx = tx; 11 | } 12 | 13 | public Transaction getTx() { 14 | return tx; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/concurrency/test/Account.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | import concurrency.stm.Ref; 4 | 5 | public class Account { 6 | private Ref moneyRef; 7 | 8 | Account(long initialMoney) { 9 | moneyRef = new Ref(initialMoney); 10 | } 11 | 12 | public void add(long amount) { 13 | moneyRef.set(moneyRef.get() + amount); 14 | } 15 | 16 | long getMoney() { 17 | return moneyRef.get(); 18 | } 19 | 20 | public Ref getRef() { 21 | return moneyRef; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/concurrency/test/Bank.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | import concurrency.stm.*; 4 | 5 | import java.util.Random; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class Bank { 11 | Account[] accounts; 12 | private Random random = new Random(); 13 | 14 | long total = 0; 15 | double charge = 0; 16 | 17 | public Bank() { 18 | randomFill(); 19 | } 20 | 21 | private void randomFill() { 22 | int NUM = 100; 23 | accounts = new Account[NUM]; 24 | for (int i = 0; i < NUM; i++) { 25 | accounts[i] = new Account(100000); 26 | } 27 | } 28 | 29 | public Account getRandomAccount() { 30 | return accounts[random.nextInt(accounts.length)]; 31 | } 32 | 33 | public int getRandomValue() { 34 | return random.nextInt(100); 35 | } 36 | 37 | public long sum() { 38 | long sum = 0; 39 | for (Account a : accounts) sum += a.getMoney(); 40 | return sum; 41 | } 42 | 43 | public void simulate(int threads, int num, TransferStrategy ts) throws Exception{ 44 | ExecutorService service = 45 | Executors.newFixedThreadPool(10); 46 | for (int i = 0; i < threads; i++) { 47 | service.submit(new BankThread(this, num, ts)); 48 | } 49 | service.shutdown(); 50 | service.awaitTermination(1, TimeUnit.MINUTES); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/concurrency/test/BankThread.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | public class BankThread implements Runnable { 4 | private Bank bank; 5 | private int num; 6 | private TransferStrategy ts; 7 | 8 | public BankThread(Bank bank, int num, TransferStrategy ts) { 9 | this.bank = bank; 10 | this.num = num; 11 | this.ts = ts; 12 | } 13 | 14 | @Override 15 | public void run() { 16 | for (int i = 0; i < num; i++) { 17 | ts.transfer(bank.getRandomAccount(), 18 | bank.getRandomAccount(), 19 | bank.getRandomValue()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/concurrency/test/NonSyncStrategy.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public class NonSyncStrategy implements TransferStrategy { 7 | @Override 8 | public void transfer(Account a, Account b, int amount) { 9 | a.add(-amount); 10 | b.add(amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/concurrency/test/RefTest.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | import concurrency.stm.GlobalContext; 4 | 5 | /** 6 | * @author mishadoff 7 | */ 8 | public class RefTest { 9 | public static void main(String[] args) { 10 | Account a = new Account(100); 11 | Account b = new Account(100); 12 | 13 | Bank bank = new Bank(); 14 | 15 | new STMStrategy().transfer(a, b, 10); 16 | new STMStrategy().transfer(a, b, 10); 17 | new STMStrategy().transfer(a, b, 10); 18 | 19 | System.out.println("A: " + a.getRef().getValue(GlobalContext.get())); 20 | System.out.println("B: " + b.getRef().getValue(GlobalContext.get())); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/concurrency/test/Runner.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | public class Runner { 4 | public static void main(String[] args) throws Exception{ 5 | TransferStrategy[] tss = new TransferStrategy[] { 6 | new NonSyncStrategy(), 7 | new SyncStrategy(), 8 | new STMStrategy() 9 | }; 10 | 11 | //Thread.sleep(1000); 12 | 13 | for (TransferStrategy ts : tss) { 14 | Bank bank = new Bank(); 15 | System.out.println(ts.getClass().getSimpleName()); 16 | System.out.println("Bank sum before: " + bank.sum()); 17 | long before = System.currentTimeMillis(); 18 | bank.simulate(100, 10000, ts); 19 | long after = System.currentTimeMillis(); 20 | System.out.println("Bank sum after: " + bank.sum()); 21 | System.out.println("Elapsed time: " + (after - before)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/concurrency/test/STMStrategy.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | import concurrency.stm.STM; 4 | import concurrency.stm.Transaction; 5 | import concurrency.stm.TransactionBlock; 6 | 7 | /** 8 | * @author mishadoff 9 | */ 10 | public class STMStrategy implements TransferStrategy { 11 | @Override 12 | public void transfer(final Account a, final Account b, final int amount) { 13 | STM.transaction(new TransactionBlock() { 14 | @Override 15 | public void run() { 16 | Transaction tx = this.getTx(); 17 | long old1 = a.getRef().getValue(tx); 18 | a.getRef().setValue(old1 - amount, tx); 19 | long old2 = b.getRef().getValue(tx); 20 | b.getRef().setValue(old2 + amount, tx); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/concurrency/test/SyncStrategy.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public class SyncStrategy implements TransferStrategy{ 7 | @Override 8 | public synchronized void transfer(Account a, Account b, int amount) { 9 | a.add(-amount); 10 | b.add(amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/concurrency/test/TransferStrategy.java: -------------------------------------------------------------------------------- 1 | package concurrency.test; 2 | 3 | /** 4 | * @author mishadoff 5 | */ 6 | public interface TransferStrategy { 7 | void transfer(final Account a, final Account b, final int amount); 8 | } 9 | -------------------------------------------------------------------------------- /stm-java.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------