├── README.md
└── src
└── com
└── assignment
├── AccountRunnable.java
├── BankAccount.java
├── LockAccount.java
├── Main.java
└── TransactionalAccount.java
/README.md:
--------------------------------------------------------------------------------
1 | BankAccountExample
2 | ==================
3 |
4 | Java multithreaded bank account example
5 |
6 | We need locks because we have a shared mutable variable (available funds). With no locks this could happen:
7 | Thread 1 : Account source has $100 avail funds wants to transfer to Account destination $30
8 | Thread 2 : Account source has $100 avail funds wants to transfer to Account destination $80
9 | Thread 1 : Checks if source has >= amount, condition is true then for some reason halts
10 | Thread 2 : Available funds are still at $100 so the condition is true and goes ahead and transfers the money
11 | Thread 1 : Resumes and since it already passed the condition it will deduct the $30 from account x - putting it at a negative amount
12 |
13 | Need to add a lock to safely update the mutable object, but might not be as straightforward
14 |
15 | This case:
16 | Thread1: sourceAccount.transferFunds(destinationAccount, 10)
17 | Thread2: destinationAccount.transferFunds(sourceAccount, 20)
18 |
19 | Solution 1:
20 | transferFunds could be declared as a synchronized function, but that's not Atomic and locks the whole function
21 |
22 | Solution 2:
23 | Lock on the source and destination account, so no simultaneous updates happen
24 |
25 | Even with those two locks deadlock could happen in this situation. Why?
26 |
27 | Thread 1: Obtains lock on source account
28 | Thread 2: Obtains lock on destination account
29 | Thread 1: Asks for lock on destination account but Thread 2 still has it
30 | Thread 2: Asks for lock on source account but Thread 1 still has it
31 | DEADLOCK
32 |
33 | -- one solution to the problem would be to provide a global lock object this would solve the deadlock problem but again not ideal
34 | -- what if I use the account id to enforce a lock order? This would prevent the deadlock
35 |
36 | Another completely different approach which I implemented is using Multiverse's implementation of transactions. Using STM instead of locks ensures
37 | atomic behavior and prevents deadlocks.
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/com/assignment/AccountRunnable.java:
--------------------------------------------------------------------------------
1 | package com.assignment;
2 |
3 | /**
4 | * AccountRunnable class
5 | * Created by saba on 2014-10-11.
6 | */
7 | public class AccountRunnable implements Runnable {
8 | private T sourceBankAccount;
9 | private T destBankAccount;
10 | private int transferAmount;
11 | private String threadName;
12 |
13 |
14 | AccountRunnable(T source, T destination, int amount, String name) {
15 | this.sourceBankAccount = source;
16 | this.destBankAccount = destination;
17 | this.transferAmount = amount;
18 | this.threadName = name;
19 | }
20 |
21 | public void run() {
22 | System.out.println(threadName + " starting transfer from " + sourceBankAccount.getId() + " to " + destBankAccount.getId());
23 | //need to check for types somehow
24 |
25 | sourceBankAccount.transferFunds(destBankAccount, transferAmount);
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/assignment/BankAccount.java:
--------------------------------------------------------------------------------
1 | package com.assignment;
2 |
3 | /**
4 | * Created by saba on 2014-10-11.
5 | */
6 | public interface BankAccount {
7 | public long getId();
8 | public void transferFunds(final T destination, int amount);
9 | public void printAvailableFunds();
10 | }
11 |
--------------------------------------------------------------------------------
/src/com/assignment/LockAccount.java:
--------------------------------------------------------------------------------
1 | package com.assignment;
2 |
3 | /**
4 | * Bank Account class using ordered locks
5 | * Created by saba on 2014-10-11.
6 | */
7 | public class LockAccount implements BankAccount {
8 | private Long id;
9 |
10 | //mutable state, yay
11 | private int availableFunds;
12 |
13 | //Constructor
14 | public LockAccount(long accountId, int accountAmount) {
15 | setId(accountId);
16 | setAvailableFunds(accountAmount);
17 | }
18 |
19 | // Getters and setters for the above fields.
20 | public long getId() {
21 | return this.id;
22 | }
23 |
24 | public void setId(long accountId){
25 | this.id = accountId;
26 | }
27 |
28 | public int getAvailableFunds() {
29 | return this.availableFunds;
30 | }
31 |
32 | public void setAvailableFunds(int amount){
33 | this.availableFunds = amount;
34 | }
35 |
36 | private void withdraw(int amount) {
37 | try {
38 | Thread.sleep(1000L); //simulating DB access
39 | } catch(InterruptedException e) {}
40 | availableFunds -= amount;
41 | }
42 |
43 | private void deposit(int amount) {
44 | try {
45 | Thread.sleep(100L); //simulating DB access
46 | } catch(InterruptedException e) {}
47 | availableFunds += amount;
48 | }
49 |
50 | private int compareAccountIds(LockAccount destination) {
51 | return this.id.compareTo(destination.id);
52 | }
53 |
54 | public void printAvailableFunds() {
55 | System.out.println("Account with id " + this.id + " has " + this.availableFunds);
56 | }
57 | /**
58 | * I broke the transferFunds function into deposit and withdraw
59 | * Lock ordering by account ids - prevents deadlock when two threads are trying
60 | * to update bank accounts of reverse order
61 | * @param destination destination account
62 | * @param amount amount of money to transfer
63 | */
64 | public void transferFunds(final LockAccount destination, int amount) {
65 | LockAccount sourceLock, destLock;
66 |
67 | int result = compareAccountIds(destination);
68 | //destination account id is greater than source account id
69 | if (result > 0) {
70 | sourceLock = destination;
71 | destLock = this;
72 | } else {
73 | sourceLock = this;
74 | destLock = destination;
75 | }
76 | System.out.println("Source lock id " + sourceLock.getId());
77 | System.out.println("Destination lock id " + destLock.getId());
78 | synchronized (sourceLock) {
79 | System.out.println("Source account lock obtained");
80 | if (this.availableFunds <= amount) {
81 | throw new IllegalArgumentException("Insufficient funds in source account with ID: " + this.id);
82 | }
83 | this.withdraw(amount);
84 | synchronized (destLock) {
85 | System.out.println("Destination account lock obtained");
86 | destination.deposit(amount);
87 | }
88 |
89 | }
90 |
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/com/assignment/Main.java:
--------------------------------------------------------------------------------
1 | package com.assignment;
2 |
3 | public class Main {
4 |
5 | public static void main(String[] args) {
6 |
7 | //Two example accounts using locks
8 | LockAccount sourceLockAccount = new LockAccount(1L, 100);
9 | LockAccount destinationLockAccount = new LockAccount(2L, 80);
10 |
11 | Thread t1 = new Thread(new AccountRunnable(sourceLockAccount, destinationLockAccount, 10, "Thread1"));
12 | Thread t2 = new Thread(new AccountRunnable(destinationLockAccount, sourceLockAccount, 20, "Thread2"));
13 | Thread t3 = new Thread(new AccountRunnable(destinationLockAccount, sourceLockAccount, 5, "Thread3"));
14 |
15 | t1.start();
16 | t2.start();
17 | t3.start();
18 |
19 | try {
20 | t1.join();
21 | t2.join();
22 | t3.join();
23 | } catch(InterruptedException e){}
24 |
25 | System.out.println("All threads are done");
26 | sourceLockAccount.printAvailableFunds();
27 | destinationLockAccount.printAvailableFunds();
28 |
29 |
30 | //Two example accounts using transactions (multiverse's implementation of STM)
31 | TransactionalAccount sourceTxnAccount = new TransactionalAccount(1L, 100);
32 | TransactionalAccount destTxnAccount = new TransactionalAccount(2L, 80);
33 |
34 | t1 = new Thread(new AccountRunnable(sourceTxnAccount, destTxnAccount, 10, "Thread1"));
35 | t2 = new Thread(new AccountRunnable(destTxnAccount, sourceTxnAccount, 20, "Thread2"));
36 | t3 = new Thread(new AccountRunnable(destTxnAccount, sourceTxnAccount, 5, "Thread3"));
37 |
38 | t1.start();
39 | t2.start();
40 | t3.start();
41 |
42 | try {
43 | t1.join();
44 | t2.join();
45 | t3.join();
46 | } catch(InterruptedException e){}
47 |
48 | System.out.println("All threads are done");
49 | sourceTxnAccount.printAvailableFunds();
50 | destTxnAccount.printAvailableFunds();
51 |
52 |
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/com/assignment/TransactionalAccount.java:
--------------------------------------------------------------------------------
1 | package com.assignment;
2 | import org.multiverse.api.references.*;
3 | import static org.multiverse.api.StmUtils.*;
4 |
5 | /**
6 | * Bank Account class using transactions instead of locks
7 | * Created by saba on 2014-10-11.
8 | */
9 | public class TransactionalAccount implements BankAccount {
10 |
11 | private TxnInteger availableFunds;
12 | private long id;
13 |
14 | public TransactionalAccount(long accountId, int balance){
15 | this.availableFunds = newTxnInteger(balance);
16 | this.id = accountId;
17 | }
18 |
19 | // Getters and setters for the above fields.
20 | public long getId() {
21 | return this.id;
22 | }
23 |
24 | public void setId(long accountId){
25 | this.id = accountId;
26 | }
27 |
28 | public TxnInteger getAvailableFunds() {
29 | return this.availableFunds;
30 | }
31 |
32 | public void setAvailableFunds(TxnInteger amount){
33 | this.availableFunds = amount;
34 | }
35 |
36 | private void withdraw(int amount) {
37 | try {
38 | Thread.sleep(1000L); //simulating DB access
39 | } catch(InterruptedException e) {}
40 | this.availableFunds.decrement(amount);
41 | }
42 |
43 | private void deposit(int amount) {
44 | try {
45 | Thread.sleep(100L); //simulating DB access
46 | } catch(InterruptedException e) {}
47 | this.availableFunds.increment(amount);
48 | }
49 |
50 | public void printAvailableFunds() {
51 | System.out.println("Account with id " + this.id + " has " + this.availableFunds.atomicToString());
52 | }
53 |
54 | public void transferFunds(final TransactionalAccount destination,final int amount) {
55 | final TransactionalAccount source = this;
56 | atomic(new Runnable() {
57 | @Override
58 | public void run() {
59 | source.withdraw(amount);
60 | destination.deposit(amount);
61 | }
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------