Implementing STM in Java

3,071 views

Published on

Presentation for beginners. Explains some concurrency basics, how Software Transactional Memory (STM) works and some implementation details.

Published in: Technology, Economy & Finance
4 Comments
15 Likes
Statistics
Notes
  • @karpolan убирай слип не убирай, всеравно ниче не получится. Синхронизировать надо в любом случае надо.
    Вот реши задачку:
    У тебя есть два акаунта А1 и А2 и есть функция трансфер(А1, А2, деньги). Напиши ее так, чтобы деньги не терялись, и трансфер вызывался из многих потоков одновременно. Я сомневаюсь что ты справишься с одной переменной globalChangingCount.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Ну убери слип и будет просто загружен процессор :) Главное что запишется только после того как предыдущие изменения пройдут. Вообще вы с этими псевдо объектами и отсутствием доступа к реальной памяти такого науралесили в Жабе и скриптовых языках, что и i7 не справляться...
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Ну во первых когда ты искуственны слип ставишь это тебе не гарантирует что значение будет то которое нужно, во вторых слип блокирующая операция, что значит если у тебя прилетело стотыщ транзакций, то потоки будут бездействовать очень долгое время.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Зочем же так все сложно? Это все в одном инстансе выполнятся будет или асинхронный год?

    Не достаточно просто в сеттере проперти (можно и в геттере, если перформанс не критичен) сделать что-то подобное?

    procedure setPropertyValue(newValue)
    {
    while (globalChangingCount > 0)
    {
    sleep(10);
    }

    globalChangingCount = globalChangingCount + 1;
    privatValue = newValue;
    globalChangingCount = globalChangingCount - 1;
    }
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
3,071
On SlideShare
0
From Embeds
0
Number of Embeds
22
Actions
Shares
0
Downloads
69
Comments
4
Likes
15
Embeds 0
No embeds

No notes for slide
  • {}
  • Implementing STM in Java

    1. 1. Implementing STM Misha Kozik @mishadoff
    2. 2. What is STM? S oftware T ransactional M emory just another one concurrency control mechanism like locks or actors
    3. 3. What is STM? S oftware T ransactional M emory Algorithm for Database-like concurrency just another one concurrency control mechanism like locks or actors
    4. 4. Database Transactions A tomicity C onsistency I solation D urability “all or nothing” data is valid at the end not affected by other txs ~persistence
    5. 5. Memory Database Transactions A tomicity C onsistency I solation D urability “all or nothing” data is valid at the end not affected by other txs ~persistence
    6. 6. Bank Accounts Transfer public class Account { private long money; } void add(long amount) { money += amount; } public class Bank { private Account[] accounts; void transfer(Account a1, Account a2, int amount) { a1.add(-amount); a2.add(amount); } } public long sum() { long sum = 0; for (Account a : accounts) sum += a.getMoney(); }
    7. 7. Simulate Process public void simulate(int threads, int num) { ExecutorService service = Executors.newFixedThreadPool(threads); for (int i = 0; i < threads; i++) { service.submit(new BankThread(this, num)); } service.shutdown(); service.awaitTermination(1, TimeUnit.MINUTES); } public class BankThread implements Runnable { private Bank bank; private int num; } public void run() { for (int i = 0; i < num; i++) { bank.transfer(bank.getRandomAccount(), bank.getRandomAccount(), bank.getRandomValue()); } }
    8. 8. Where is my money?! Bank bank = new Bank(); System.out.println("Bank sum before: " + bank.sum()); bank.simulate(100, 100000); System.out.println("Bank sum after: " + bank.sum()); Bank sum before: Bank sum after: 10000000 9970464 -20000$ = 49634
    9. 9. How to solve it?
    10. 10. synchronized
    11. 11. What the difference? public class Bank { synchronized void transfer(Account a1, Account a2, int amt) { a1.add(-amt); a2.add(amt); } } VS public class Account { synchronized void transfer(Account a, int amt) { this.add(-amt); a.add(amt); } } synchronized void add(int amt) { value += amt; }
    12. 12. synchronized: cons → no read/write distinction → slow/error-prone → low-level → protect code rather than data
    13. 13. Locks
    14. 14. still use locks in 2013 RingBuffer buffer = null; readCpsLock.lock(); try { if (ticks.containsKey(accountId)) { buffer = ticks.get(accountId); // check if this value changed if (buffer.getMaxCallCount() != cpsValue) { readCpsLock.unlock(); try { writeCpsLock.lock(); try { ticks.remove(accountId); buffer = new RingBuffer(cpsValue, DEFAULT_TIME_UNIT); ticks.put(accountId, buffer); } finally { writeCpsLock.unlock(); } } finally { readCpsLock.lock(); } } } else { buffer = new RingBuffer(cpsValue, DEFAULT_TIME_UNIT); readCpsLock.unlock(); try { writeCpsLock.lock(); try { ticks.put(accountId, buffer); } finally { writeCpsLock.unlock(); } } finally { readCpsLock.lock(); } } } finally { readCpsLock.unlock(); } useful code
    15. 15. Locks: cons → tricky → error-prone → not composable → protect code rather than data
    16. 16. Actors
    17. 17. Actors in Erlang ping(0, PID) -> PID ! finished, io:format("Ping stop~n", []); ping(N, PID) -> PID ! {ping, self()}, receive pong -> io:format("Ping~n", []) end, ping(N - 1, PID). pong() -> receive finished -> io:format("Pong stop~n", []); {ping, PID} -> io:format("Pong~n", []), PID ! pong, pong() end. start() -> PID = spawn(pingpong, pong, []), spawn(pingpong, ping, [3, PID]).
    18. 18. Actors: cons → code restructuring → deadlocks still possible
    19. 19. STM
    20. 20. STM in Clojure (def account (ref 100)) (defn transfer [acc1 acc2 amount] (dosync (alter acc1 - amount) (alter acc2 + amount)))
    21. 21. STM in Java transaction { acc1.add(-value); acc2.add(value); }
    22. 22. STM in Java STM.transaction( new Runnable() { public void run() { acc1.add(-value); acc2.add(value); } });
    23. 23. STM in Java 8 STM.transaction(() → { acc1.add(-value); acc2.add(value); });
    24. 24. STM: pros → natural approach → coordinated state → no deadlocks, no livelocks
    25. 25. Implementing STM
    26. 26. Intuition → start transaction → get ref snapshot → set of reads/writes* on snapshot → CAS initial and current snapshot → if CAS succeeds commit → else retry
    27. 27. Ref v0.1 public final class Ref<T> { private T value; public T getValue() { return value; } } public void setValue(T value) { this.value = value; }
    28. 28. Ref v0.1 public final class Ref<T> { private T value; public T getValue() { return value; } } // from anywhere public void setValue(T value) { this.value = value; } // from transaction
    29. 29. Context Transaction <T> void set(Ref<T> ref, T value); <T> T get(Ref<T> ref); GlobalContext singleton
    30. 30. Ref v0.2 public final class Ref<T> { private T value; public T getValue(Context ctx) { return ctx.get(this); } public void setValue(T value, Transaction tx) { tx.set(this, value); } }
    31. 31. STM API public final class STM { private STM() {} public static void transaction(TransactionBlock block) { Transaction tx = new Transaction(); block.setTx(tx); block.run(); } } public abstract class TransactionBlock implements Runnable { private Transaction tx; void setTx(Transaction tx) { this.tx = tx; } } public Transaction getTx() { return tx; }
    32. 32. STM API public final class STM { private STM() {} public static void transaction(TransactionBlock block) { Transaction tx = new Transaction(); block.setTx(tx); block.run(); } NO return value (1) } public abstract class TransactionBlock implements Runnable { private Transaction tx; void setTx(Transaction tx) { this.tx = tx; } } public Transaction getTx() { return tx; }
    33. 33. STM Usage STM.transaction(new TransactionBlock() { @Override public void run() { int old1 = a1.getValue(this.getTx()); a1.setValue(old1 - value, this.getTx()); int old2 = a2.getValue(this.getTx()); a2.setValue(old2 + value, this.getTx()); } });
    34. 34. STM Usage STM.transaction(new TransactionBlock() { @Override public void run() { int old1 = a1.getValue(this.getTx()); a1.setValue(old1 - value, this.getTx()); int old2 = a2.getValue(this.getTx()); a2.setValue(old2 + value, this.getTx()); } }); What the mess? (2)
    35. 35. GlobalContext v0.1 public class GlobalContext extends Context { private HashMap<Ref, Object> refs; } @Override <T> T get(Ref<T> ref) { return (T)refs.get(ref); }
    36. 36. GlobalContext v0.1 public class GlobalContext extends Context { private HashMap<Ref, Object> refs; } @Override <T> T get(Ref<T> ref) { return (T)refs.get(ref); } need control over ref map cheat (3)
    37. 37. Ref v0.3 public final class Ref<T> { private T value; public T getValue(Context ctx) { return ctx.get(this); } public void setValue(T value, Transaction tx) { tx.set(this, value); } }
    38. 38. GlobalContext v0.2 public class GlobalContext extends Context { private HashMap<Ref, Object> refs; } @Override <T> T get(Ref<T> ref) { return ref.value; }
    39. 39. Transaction v0.1 public final class Transaction extends Context { private HashMap<Ref, Object> inTxMap; @Override <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); } return (T)inTxMap.get(ref); } } <T> void set(Ref<T> ref, T value) { inTxMap.put(ref, value); }
    40. 40. Transaction v0.1 public final class Transaction extends Context { private HashMap<Ref, Object> inTxMap; NO Snapshot Isolation (4) @Override <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); } return (T)inTxMap.get(ref); } } <T> void set(Ref<T> ref, T value) { inTxMap.put(ref, value); }
    41. 41. Transaction v0.1 public final class Transaction extends Context { private HashMap<Ref, Object> inTxMap; Separate reads @Override <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); } return (T)inTxMap.get(ref); } } <T> void set(Ref<T> ref, T value) { inTxMap.put(ref, value); } & writes
    42. 42. Transaction v0.2 public final class Transaction extends Context { private HashMap<Ref, Object> inTxMap; private HashSet<Ref> toUpdate; @Override <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); } return (T)inTxMap.get(ref); } } <T> void set(Ref<T> ref, T value) { inTxMap.put(ref, value); toUpdate.add(ref); }
    43. 43. Commit public final class Transaction extends Context { // … void commit() { synchronized (STM.commitLock ) { // TODO validate // TODO write values } } } public final class STM { private STM() {} public static Object commitLock = new Object(); } // ...
    44. 44. Commit: Write Values void commit() { synchronized (STM.commitLock) { // TODO validate for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); } } }
    45. 45. Validation Idea → get all transaction-local refs → compare version with transaction revision → if all versions the same allow commit → else retry
    46. 46. Ref v0.4 public final class Ref<T> { T value; long revision = 0; // ... }
    47. 47. Ref v0.4 public final class Ref<T> { T value; long revision = 0; // ... } No history (5)
    48. 48. Transaction v0.3 public final class Transaction extends Context { // ... private long revision; private static AtomicLong transactionNum = new AtomicLong(0); Transaction() { revision = transactionNum.incrementAndGet(); } // ... }
    49. 49. Transaction v0.3 public final class Transaction extends Context { private HashMap<Ref, Long> version; <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); if (!version.containsKey(ref)) { version.put(ref, ref.revision); } } return (T)inTxMap.get(ref); } } <T> void set(Ref<T> ref, T value) { inTxMap.put(ref, value); toUpdate.add(ref); if (!version.containsKey(ref)) { version.put(ref, ref.revision); } }
    50. 50. Commit: Validation boolean commit() { synchronized (STM.commitLock) { boolean isValid = true; for (Ref ref : inTxMap.keySet()) { if (ref.revision != version.get(ref)) { isValid = false; break; } } } } if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } return isValid;
    51. 51. Commit: Validation boolean commit() { synchronized (STM.commitLock) { boolean isValid = true; for (Ref ref : inTxMap.keySet()) { if (ref.revision != version.get(ref)) { isValid = false; break; } } } } if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } return isValid;
    52. 52. Commit: Validation boolean commit() { synchronized (STM.commitLock) { boolean isValid = true; for (Ref ref : inTxMap.keySet()) { if (ref.revision != version.get(ref)) { isValid = false; break; } } } } if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } return isValid; Transaction revision
    53. 53. Commit: Validation boolean commit() { synchronized (STM.commitLock) { boolean isValid = true; for (Ref ref : inTxMap.keySet()) { if (ref.revision != version.get(ref)) { isValid = false; break; } } } } if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } return isValid;
    54. 54. STM Transaction public final class STM { private STM() {} public static Object commitLock = new Object(); public static void transaction(TransactionBlock block) { boolean committed = false; while (!committed) { Transaction tx = new Transaction(); block.setTx(tx); block.run(); committed = tx.commit(); } } }
    55. 55. STM Transaction public final class STM { private STM() {} public static Object commitLock = new Object(); public static void transaction(TransactionBlock block) { boolean committed = false; while (!committed) { Transaction tx = new Transaction(); block.setTx(tx); block.run(); committed = tx.commit(); } } } No exceptions handling (6) No nested transactions (7)
    56. 56. What the problem? boolean commit() { synchronized (STM.commitLock) { // ... if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } // ... } }
    57. 57. What the problem? Memory switch is atomic boolean commit() { synchronized (STM.commitLock) { // ... if (isValid) { for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } } // ... } Two Memory switches } are not atomic
    58. 58. RefTuple public class RefTuple<V, R> { V value; R revision; public RefTuple(V v, R r) { this.value = v; this.revision = r; } static <V, R> RefTuple get(V v, R r) { return new RefTuple<V, R>(v, r); } }
    59. 59. Ref v0.5 public final class Ref<T> { RefTuple<T, Long> content; public Ref(T value) { content = RefTuple.get(value, 0); } // ... }
    60. 60. Transaction.commit() for (Ref ref : toUpdate) { ref.value = inTxMap.get(ref); ref.revision = revision; } for (Ref ref : toUpdate) { ref.content = RefTuple.get(inTxMap.get(ref), revision); }
    61. 61. Transaction.get() <T> T get(Ref<T> ref) { 2 non-sync reads if (!inTxMap.containsKey(ref)) { inTxMap.put(ref, ref.value); if (!version.containsKey(ref)) { version.put(ref, ref.revision); } } return (T)inTxMap.get(ref); } <T> T get(Ref<T> ref) { if (!inTxMap.containsKey(ref)) { RefTuple<T, Long> tuple = ref.content; inTxMap.put(ref, tuple.value); if (!version.containsKey(ref)) { version.put(ref, tuple.revision); } } return (T)inTxMap.get(ref); }
    62. 62. // TODO 1. 2. 3. 4. 5. 6. 7. Support return value in TX Avoid this.getTx() repetition Fair GlobalContext Snapshot Isolation Support ref history Support exceptions Support nested transactions
    63. 63. STM Algorithms Multi-Version Concurrency Control Lazy Snapshot Algorithm Transactional Locking 2
    64. 64. Practical Advices → avoid side-effects → avoid short/long transactions → separate as much as possible outside of transaction → ref value must be immutable OR STM WON'T HELP YOU
    65. 65. “But STM is slow...”
    66. 66. TM/GC Analogy Transactional Memory is to shared-memory concurrency as Garbage Collection is to memory management.
    67. 67. Non-STM → atomics → immutability → avoid shared state → lock-free data structures
    68. 68. Links → STM In Clojure → Locks, Actors and STM in pictures → The Dining Philosophers Problem → Clojure refs → TM/GC Analogy [PDF] → STM in Scala → 7 Concurrency Models in 7 weeks

    ×