├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── ua
│ └── yet
│ └── adv
│ └── java
│ └── concurrency
│ ├── Account.java
│ ├── OperationsCompletableFuture.java
│ ├── OperationsDeadlock.java
│ ├── OperationsSemaphore.java
│ ├── OperationsService.java
│ ├── OperationsSynchronizers.java
│ └── transfers
│ ├── SimpleTransfer.java
│ └── Transfer.java
└── test
└── java
└── ua
└── yet
└── adv
└── java
└── concurrency
├── TestAccount.java
├── TestDeadlock.java
└── TestSimpleTransfer.java
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.settings
3 | .classpath
4 | .project
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Yuriy Tkach
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Sample code from the Advanced Java Concurrency training
2 |
3 | The code showcases different scenarios for multithreaded processing of some simple logic using synchronizations, locks, and different classes from java.util.concurrent..
4 | The video for the training in Russian language can be found here: https://www.youtube.com/playlist?list=PL6jg6AGdCNaXo06LjCBmRao-qJdf38oKp
5 |
6 | The code has some unit tests that validate the logic.
7 |
8 | There are also some classes with `main` function in them that output information to console. You can call them in the following way:
9 | - `mvn exec:java -Dexec.mainClass="ua.yet.adv.java.concurrency.OperationsDeadlock"`
10 | - `mvn exec:java -Dexec.mainClass="ua.yet.adv.java.concurrency.OperationsService"`
11 | - `mvn exec:java -Dexec.mainClass="ua.yet.adv.java.concurrency.OperationsCompletableFuture"`
12 | - `mvn exec:java -Dexec.mainClass="ua.yet.adv.java.concurrency.OperationsSemaphore"`
13 | - `mvn exec:java -Dexec.mainClass="ua.yet.adv.java.concurrency.OperationsSynchronizers"`
14 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | ua.yet.adv.java
5 | concurrency
6 | 1.0-SNAPSHOT
7 | advanced_java_concurrency
8 | Sample code from the Advanced Java Concurrency training
9 |
10 |
11 | 1.8
12 | UTF-8
13 |
14 | 3.0.1
15 | 4.12
16 |
17 |
18 |
19 |
20 |
21 | org.apache.maven.plugins
22 | maven-compiler-plugin
23 | 3.3
24 |
25 | ${java.version}
26 | ${java.version}
27 | ${java.version}
28 | ${project.build.sourceEncoding}
29 | true
30 | true
31 | true
32 |
33 |
34 |
35 | org.apache.maven.plugins
36 | maven-source-plugin
37 | 2.2.1
38 |
39 | 120
40 | true
41 |
42 |
43 |
44 | attach-sources
45 |
46 | jar
47 |
48 |
49 |
50 |
51 |
52 | org.apache.maven.plugins
53 | maven-resources-plugin
54 | 2.6
55 |
56 | ${project.build.sourceEncoding}
57 |
58 |
59 |
60 | org.codehaus.mojo
61 | findbugs-maven-plugin
62 | 3.0.3
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.codehaus.mojo
71 | findbugs-maven-plugin
72 | 3.0.3
73 |
74 |
76 | Max
77 |
78 | false
79 |
80 | Low
81 |
82 | true
83 |
84 | ${project.build.directory}/findbugs
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | com.google.code.findbugs
93 | jsr305
94 | ${findbugs.version}
95 |
96 |
97 |
98 | junit
99 | junit
100 | ${junit.version}
101 | test
102 |
103 |
104 |
105 | GIT
106 | https://github.com/yuriytkach/advanced_java_concurrency/issues
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/Account.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.util.concurrent.atomic.LongAdder;
4 | import java.util.concurrent.locks.Lock;
5 | import java.util.concurrent.locks.ReentrantLock;
6 |
7 | public class Account {
8 |
9 | private final int id;
10 |
11 | private int balance;
12 |
13 | private final LongAdder failCounter = new LongAdder();
14 |
15 | private final Lock lock = new ReentrantLock();
16 |
17 | public Account(int accountId, int initialBalance) {
18 | this.id = accountId;
19 | this.balance = initialBalance;
20 | }
21 |
22 | public void deposit(final int amount) {
23 | balance += amount;
24 | }
25 |
26 | public void withdraw(final int amount) {
27 | balance -= amount;
28 | }
29 |
30 | public void incFailedTransferCount() {
31 | failCounter.increment();
32 | }
33 |
34 | public int getId() {
35 | return id;
36 | }
37 |
38 | public int getBalance() {
39 | return balance;
40 | }
41 |
42 | public long getFailCount() {
43 | return failCounter.sum();
44 | }
45 |
46 | public Lock getLock() {
47 | return lock;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/OperationsCompletableFuture.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.CompletableFuture;
5 | import java.util.concurrent.atomic.LongAdder;
6 |
7 | import ua.yet.adv.java.concurrency.transfers.SimpleTransfer;
8 |
9 | public class OperationsCompletableFuture {
10 |
11 | public static void main(String[] args) {
12 | new OperationsCompletableFuture().runTransfers(10);
13 | }
14 |
15 | private final Account acc1 = new Account(1, 1000);
16 | private final Account acc2 = new Account(2, 500);
17 | private final Account acc3 = new Account(3, 500);
18 |
19 | private final Random rnd = new Random();
20 |
21 | private LongAdder totalTransferAttempts = new LongAdder();
22 | private LongAdder totalUnsuccessTransfers = new LongAdder();
23 |
24 | public void runTransfers(int count) {
25 | CompletableFuture>[] futures = new CompletableFuture[count];
26 |
27 | for (int i = 0; i < count; i++) {
28 | CompletableFuture futureAmount = CompletableFuture.supplyAsync(()->requestTransferAmount());
29 |
30 | CompletableFuture futureTransfer = CompletableFuture.supplyAsync(()->requestDestinationAccount())
31 | .thenCombineAsync(futureAmount, (acc2, amount) -> createTransfer(acc1, acc2, amount))
32 | .thenApplyAsync(transfer -> transfer.perform())
33 | .exceptionally(ex -> processTransferException(ex))
34 | .thenAcceptAsync(result -> processTransferResult(result))
35 | .thenRunAsync(() -> updateTransferCount());
36 |
37 | futures[i] = futureTransfer;
38 | }
39 |
40 | System.out.println("Waiting for all...");
41 | CompletableFuture.allOf(futures).join();
42 |
43 | System.out.println("Done.");
44 | System.out.println("Total transfers: " + totalTransferAttempts);
45 | System.out.println("Total failures: " + totalUnsuccessTransfers);
46 | System.out.println("Total failures on acc1: " + acc1.getFailCount());
47 | }
48 |
49 | private void updateTransferCount() {
50 | System.out.println("Updated transfer count. Thread " + Thread.currentThread().getId());
51 | totalTransferAttempts.increment();
52 | }
53 |
54 | private void processTransferResult(Boolean result) {
55 | System.out.println("Transfer result: " + result + ". Thread " + Thread.currentThread().getId());
56 | }
57 |
58 | private Account requestDestinationAccount() {
59 | System.out.println("Request amount. Thread " + Thread.currentThread().getId());
60 | return rnd.nextInt(1) == 1 ? acc2 : acc3;
61 | }
62 |
63 | private int requestTransferAmount() {
64 | System.out.println("Request amount. Thread " + Thread.currentThread().getId());
65 | return rnd.nextInt(400);
66 | }
67 |
68 | private SimpleTransfer createTransfer(Account acc1, Account acc2, int amount) {
69 | System.out.println("Creating transfer. Thread " + Thread.currentThread().getId());
70 | return new SimpleTransfer(acc1, acc2, amount);
71 | }
72 |
73 | private Boolean processTransferException(Throwable ex) {
74 | System.out.println("Failed transfer: " + ex.getMessage() + ". Thread " + Thread.currentThread().getId());
75 | totalUnsuccessTransfers.increment();
76 | return false;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/OperationsDeadlock.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | /**
4 | * Deadlock simulation by acquiring resources in wrong order
5 | *
6 | * @author yuriy
7 | */
8 | public class OperationsDeadlock {
9 |
10 | public static void main(String[] args) {
11 |
12 | final Account a = new Account(1, 1000);
13 | final Account b = new Account(2, 2000);
14 |
15 | Thread t1 = new Thread(() -> transfer(a, b, 500) );
16 |
17 | Thread t2 = new Thread(() -> transfer(b, a, 300) );
18 |
19 | outputAmount(a);
20 | outputAmount(b);
21 | System.out.println("OperationsDeadlock...");
22 |
23 | t1.start();
24 | t2.start();
25 |
26 | try {
27 | t1.join();
28 | t2.join();
29 | } catch (Exception e) {
30 | throw new IllegalStateException(e);
31 | }
32 |
33 | outputAmount(a);
34 | outputAmount(b);
35 | }
36 |
37 | private static void outputAmount(Account acc) {
38 | System.out.println("Account " + acc.getId() + " balance: "
39 | + acc.getBalance());
40 | }
41 |
42 | static void transfer(Account from, Account to, int amount) {
43 |
44 | synchronized (from) {
45 | System.out.println("Acquired " + from.getId());
46 |
47 | try {
48 | Thread.sleep(100);
49 | } catch (InterruptedException e) {
50 | e.printStackTrace();
51 | }
52 |
53 | synchronized (to) {
54 | System.out.println("Acquired " + to.getId());
55 | from.withdraw(amount);
56 | to.deposit(amount);
57 | }
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/OperationsSemaphore.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.util.concurrent.ExecutorService;
4 | import java.util.concurrent.Executors;
5 | import java.util.concurrent.Semaphore;
6 |
7 | /**
8 | * Small showcase for using the semaphore. Only 2 workers are have permits to
9 | * work. Later (after 200 milliseconds) the release() is called from outside
10 | * thread thus adding one more permit. The fourth worker will start after first
11 | * one will release the permit.
12 | *
13 | * @author yuriy
14 | */
15 | public class OperationsSemaphore {
16 |
17 | static private Semaphore sem = new Semaphore(2);
18 |
19 | /**
20 | * @param args
21 | */
22 | public static void main(String[] args) {
23 |
24 | ExecutorService service = Executors.newCachedThreadPool();
25 | service.execute(new Worker(1));
26 | service.execute(new Worker(2));
27 | service.execute(new Worker(3));
28 | service.execute(new Worker(4));
29 |
30 | service.execute(new Runnable() {
31 |
32 | @Override
33 | public void run() {
34 | try {
35 | Thread.sleep(200);
36 | } catch (InterruptedException e) {
37 | }
38 | System.out.println("releasing...");
39 | sem.release();
40 | }
41 | });
42 |
43 | service.shutdown();
44 |
45 | }
46 |
47 | static class Worker extends Thread {
48 |
49 | private final int id;
50 |
51 | public Worker(int id) {
52 | super();
53 | this.id = id;
54 | }
55 |
56 | @Override
57 | public void run() {
58 | System.out.println(id + " - trying to aquire...");
59 | try {
60 | sem.acquire();
61 | System.out.println(id + " - aquired");
62 | sleep(1000);
63 | sem.release();
64 | System.out.println(id + " - released");
65 | } catch (InterruptedException e) {
66 | System.err.println(id + " - interrupted");
67 | }
68 | }
69 |
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/OperationsService.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Random;
6 | import java.util.concurrent.ExecutionException;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.Future;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | import ua.yet.adv.java.concurrency.transfers.SimpleTransfer;
14 |
15 | /**
16 | * Showcase for using ExecutorService to start threads in fixed thread pool. In
17 | * addition to that a scheduled thread is started every 3 secs to poll for
18 | * unsuccessful transfers.
19 | *
20 | * @author yuriy
21 | */
22 | public class OperationsService {
23 |
24 | public static void main(String[] args) throws InterruptedException {
25 | final Account acc1 = new Account(1, 1000);
26 | final Account acc2 = new Account(2, 1000);
27 |
28 | Random rnd = new Random();
29 |
30 | ScheduledExecutorService amountMonitoring = createSuccessMonitoringThread(acc1);
31 |
32 | List transfers = new ArrayList<>();
33 | for (int i = 0; i < 10; i++) {
34 | transfers.add(new SimpleTransfer(acc1, acc2, rnd.nextInt(400)));
35 | }
36 |
37 | ExecutorService service = Executors.newFixedThreadPool(3);
38 | List> rez = service.invokeAll(transfers);
39 | service.shutdown();
40 |
41 | // Will throw exception if uncommented
42 | // service.submit(new Transfer(acc2, acc1, 500));
43 |
44 | System.out.println("Future results:");
45 |
46 | for (int i=0; i < rez.size(); i++) {
47 | Future future = rez.get(i);
48 | SimpleTransfer transfer = transfers.get(i);
49 | try {
50 | System.out.println("[" + transfer.getId() + "] Transfer: " + future.get());
51 | } catch (ExecutionException e) {
52 | System.out.println("[" + transfer.getId() + "] Transfer: " + e.getMessage());
53 | }
54 | }
55 |
56 | amountMonitoring.shutdown();
57 | }
58 |
59 | private static ScheduledExecutorService createSuccessMonitoringThread(
60 | final Account acc1) {
61 | ScheduledExecutorService amountMonitoring = Executors
62 | .newScheduledThreadPool(1);
63 | amountMonitoring.scheduleAtFixedRate(new Runnable() {
64 | public void run() {
65 | System.out.println("Failed transfers in Account 1: "
66 | + acc1.getFailCount());
67 | }
68 | }, 2, 3, TimeUnit.SECONDS);
69 | return amountMonitoring;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/OperationsSynchronizers.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.CountDownLatch;
5 | import java.util.concurrent.CyclicBarrier;
6 | import java.util.concurrent.ExecutorService;
7 | import java.util.concurrent.Executors;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import ua.yet.adv.java.concurrency.transfers.Transfer;
11 |
12 | public class OperationsSynchronizers {
13 |
14 | private static final int NUM_A_B_TRANSFERS = 3;
15 | private static final int NUM_B_A_TRANSFERS = 4;
16 |
17 | static volatile long transferAbStart;
18 | static volatile long transferAbEnd;
19 |
20 | /**
21 | * @param args
22 | * @throws InterruptedException
23 | */
24 | public static void main(String[] args) throws InterruptedException {
25 | final Account accA = new Account(1, 1000);
26 | final Account accB = new Account(2, 1000);
27 |
28 | Random rnd = new Random();
29 |
30 | transferAbStart = System.currentTimeMillis();
31 |
32 | // Latch to start all threads simultaneously
33 | CountDownLatch startLatch = new CountDownLatch(1);
34 |
35 | // Latch to start b->a transfers after a->b transfers
36 | CountDownLatch baLatch = new CountDownLatch(NUM_A_B_TRANSFERS);
37 |
38 | // Barrier to count overall times for a->b transfers
39 | CyclicBarrier abBarrier = new CyclicBarrier(NUM_A_B_TRANSFERS,
40 | new Runnable() {
41 | @Override
42 | public void run() {
43 | transferAbEnd = System.currentTimeMillis();
44 | }
45 | });
46 |
47 | // We need to have all threads working as we use cyclic barrier to wait
48 | // inside them
49 | ExecutorService service = Executors.newCachedThreadPool();
50 |
51 | // All a->b transfers have startLatch to start simultaneously,
52 | // baLatch to count down for b->a transfers, and abBarrier to count time
53 | for (int i = 0; i < NUM_A_B_TRANSFERS; i++) {
54 | service.submit(new Transfer(accA, accB, rnd.nextInt(400), true,
55 | startLatch, baLatch, abBarrier));
56 | }
57 |
58 | for (int i = 0; i < NUM_B_A_TRANSFERS; i++) {
59 | service.submit(new Transfer(accB, accA, rnd.nextInt(400), true,
60 | baLatch, null, null));
61 | }
62 |
63 | service.shutdown();
64 |
65 | // Starting all threads:
66 | startLatch.countDown();
67 |
68 | // Waiting for all tasks to complete
69 | boolean rezWait = service.awaitTermination(
70 | (NUM_A_B_TRANSFERS + NUM_B_A_TRANSFERS) * 2, TimeUnit.SECONDS);
71 |
72 | if (!rezWait) {
73 | System.err.println("Not all tasks have completed.");
74 | }
75 |
76 | System.out.println("Overall time for A->B transfers is: "
77 | + (transferAbEnd - transferAbStart) + " ms");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/transfers/SimpleTransfer.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency.transfers;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.Callable;
5 | import java.util.concurrent.TimeUnit;
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | import javax.annotation.Nonnull;
9 |
10 | import ua.yet.adv.java.concurrency.Account;
11 |
12 | public class SimpleTransfer implements Callable {
13 |
14 | private static final AtomicInteger idGenerator = new AtomicInteger(1);
15 |
16 | private static final int LOCK_WAIT_SEC = 5;
17 |
18 | private static final int MAX_TRANSFER_SEC = 7;
19 |
20 | private final int id;
21 |
22 | private final Account accFrom;
23 | private final Account accTo;
24 | private final int amount;
25 |
26 | private final Random waitRandom = new Random();
27 |
28 | public SimpleTransfer(@Nonnull Account accFrom, @Nonnull Account accTo, int amount) {
29 | this.id = idGenerator.getAndIncrement();
30 |
31 | this.accFrom = accFrom;
32 | this.accTo = accTo;
33 | this.amount = amount;
34 | }
35 |
36 | @Override
37 | public Boolean call() throws Exception {
38 | if (accFrom.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS)) {
39 | try {
40 | if (accFrom.getBalance() < amount) {
41 | accFrom.incFailedTransferCount();
42 | throw new IllegalStateException("Insufficient funds in Account " + accFrom.getId());
43 | }
44 |
45 | if (accTo.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS)) {
46 | try {
47 |
48 | accFrom.withdraw(amount);
49 | accTo.deposit(amount);
50 |
51 | Thread.sleep(waitRandom.nextInt(MAX_TRANSFER_SEC*1000));
52 |
53 | System.out.println("[" + id + "] " + "Transfer " + amount + " done from " + accFrom.getId()
54 | + " to " + accTo.getId());
55 |
56 | return true;
57 |
58 | } finally {
59 | accTo.getLock().unlock();
60 | }
61 | } else {
62 | accTo.incFailedTransferCount();
63 | return false;
64 | }
65 | } finally {
66 | accFrom.getLock().unlock();
67 | }
68 | } else {
69 | accFrom.incFailedTransferCount();
70 | return false;
71 | }
72 | }
73 |
74 | public int getId() {
75 | return id;
76 | }
77 |
78 | public Boolean perform() {
79 | System.out.println("Performing transfer. Thread " + Thread.currentThread().getId());
80 | try {
81 | return call();
82 | } catch (Exception e) {
83 | throw new IllegalStateException(e.getMessage());
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/ua/yet/adv/java/concurrency/transfers/Transfer.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency.transfers;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.Callable;
5 | import java.util.concurrent.CountDownLatch;
6 | import java.util.concurrent.CyclicBarrier;
7 | import java.util.concurrent.TimeUnit;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | import ua.yet.adv.java.concurrency.Account;
11 |
12 | public class Transfer implements Callable {
13 |
14 | private static final AtomicInteger idGenerator = new AtomicInteger(1);
15 |
16 | private static final int LOCK_WAIT_SEC = 5;
17 |
18 | private final int id;
19 |
20 | private final Account accFrom;
21 | private final Account accTo;
22 | private final int amount;
23 |
24 | private final Random waitRandom = new Random();
25 |
26 | private boolean retryFail;
27 | private CountDownLatch startLatch;
28 | private CountDownLatch endLatch;
29 | private CyclicBarrier endBarrier;
30 |
31 | public Transfer(Account accFrom, Account accTo, int amount) {
32 | this.id = idGenerator.getAndIncrement();
33 |
34 | this.accFrom = accFrom;
35 | this.accTo = accTo;
36 | this.amount = amount;
37 | }
38 |
39 | public Transfer(Account accFrom, Account accTo, int amount,
40 | boolean retryFail, CountDownLatch startLatch,
41 | CountDownLatch endLatch, CyclicBarrier endBarrier) {
42 | this(accFrom, accTo, amount);
43 | this.retryFail = retryFail;
44 | this.startLatch = startLatch;
45 | this.endLatch = endLatch;
46 | this.endBarrier = endBarrier;
47 | }
48 |
49 | @Override
50 | public Boolean call() throws Exception {
51 |
52 | // waiting before start
53 | if (startLatch != null) {
54 | System.out.println("[" + id + "] " + "Waiting to start...");
55 | startLatch.await();
56 | }
57 |
58 | for (;;) {
59 | if (accFrom.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS)) {
60 | try {
61 | if (accTo.getLock()
62 | .tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS)) {
63 |
64 | try {
65 | if (accFrom.getBalance() < amount) {
66 | throw new IllegalStateException("[" + id
67 | + "] " + "Failed to transfer " + amount
68 | + " from Account " + accFrom.getId()
69 | + " (Balance is "
70 | + accFrom.getBalance() + ")");
71 | }
72 |
73 | accFrom.withdraw(amount);
74 | accTo.deposit(amount);
75 |
76 | Thread.sleep(waitRandom.nextInt(2000));
77 |
78 | System.out.println("[" + id + "] " + "Transfer "
79 | + amount + " done from " + accFrom.getId()
80 | + " to " + accTo.getId());
81 |
82 | return true;
83 |
84 | } finally {
85 | accFrom.getLock().unlock();
86 | accTo.getLock().unlock();
87 |
88 | // count down after finish
89 | if (endLatch != null) {
90 | endLatch.countDown();
91 | }
92 | // Waiting for others to finish
93 | if (endBarrier != null) {
94 | endBarrier.await();
95 | }
96 | }
97 |
98 | } else {
99 | accTo.incFailedTransferCount();
100 | if (!retryFail) {
101 | return false;
102 | }
103 | }
104 | } finally {
105 | accFrom.getLock().unlock();
106 | }
107 | } else {
108 | accFrom.incFailedTransferCount();
109 | if (!retryFail) {
110 | return false;
111 | }
112 | }
113 | }
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/test/java/ua/yet/adv/java/concurrency/TestAccount.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | public class TestAccount {
8 |
9 | @Test
10 | public void testBalance() {
11 | Account acc = new Account(1, 1000);
12 |
13 | acc.deposit(500);
14 | assertEquals(1500, acc.getBalance());
15 |
16 | acc.withdraw(100);
17 | assertEquals(1400, acc.getBalance());
18 | }
19 |
20 | public void testIncFailedTransferCount() {
21 | Account acc = new Account(1, 1);
22 | assertEquals(0, acc.getFailCount());
23 |
24 | acc.incFailedTransferCount();
25 | assertEquals(1, acc.getFailCount());
26 |
27 | acc.incFailedTransferCount();
28 | acc.incFailedTransferCount();
29 | assertEquals(3, acc.getFailCount());
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/ua/yet/adv/java/concurrency/TestDeadlock.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import java.lang.management.ManagementFactory;
4 | import java.lang.management.ThreadInfo;
5 | import java.lang.management.ThreadMXBean;
6 |
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 |
10 | public class TestDeadlock {
11 |
12 | @Test
13 | public void testDeadlock() throws InterruptedException {
14 | new Thread(() -> OperationsDeadlock.main(null)).start();
15 |
16 | // Sleeping for deadlock to happen
17 | Thread.sleep(1000);
18 |
19 | // Programmatic way to detect deadlock
20 |
21 | ThreadMXBean bean = ManagementFactory.getThreadMXBean();
22 | long[] threadIds = bean.findDeadlockedThreads(); // Returns null if no
23 | // threads are
24 | // deadlocked.
25 |
26 | Assert.assertNotNull(threadIds);
27 | Assert.assertEquals(2, threadIds.length);
28 |
29 | ThreadInfo[] infos = bean.getThreadInfo(threadIds);
30 |
31 | for (ThreadInfo info : infos) {
32 | System.out.println(info);
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/ua/yet/adv/java/concurrency/TestSimpleTransfer.java:
--------------------------------------------------------------------------------
1 | package ua.yet.adv.java.concurrency;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertFalse;
5 | import static org.junit.Assert.assertTrue;
6 | import static org.junit.Assert.fail;
7 |
8 | import org.junit.Test;
9 |
10 | import ua.yet.adv.java.concurrency.transfers.SimpleTransfer;
11 |
12 | public class TestSimpleTransfer {
13 |
14 | @Test
15 | public void testTransferNoLocks() throws Exception {
16 | Account acc1 = new Account(1, 100);
17 | Account acc2 = new Account(2, 50);
18 |
19 | SimpleTransfer transfer = new SimpleTransfer(acc1, acc2, 10);
20 | boolean result = transfer.call();
21 |
22 | assertTrue(result);
23 | assertEquals(90, acc1.getBalance());
24 | assertEquals(60, acc2.getBalance());
25 | assertEquals(0, acc1.getFailCount());
26 | assertEquals(0, acc2.getFailCount());
27 | }
28 |
29 | @Test(expected = IllegalStateException.class)
30 | public void testTransferNoFunds() throws Exception {
31 | Account acc1 = new Account(1, 100);
32 | Account acc2 = new Account(2, 50);
33 |
34 | SimpleTransfer transfer = new SimpleTransfer(acc1, acc2, 1000);
35 | transfer.call();
36 | }
37 |
38 | @Test
39 | public void testTransferLockedFrom() throws Exception {
40 | Account acc1 = new Account(1, 100);
41 | Account acc2 = new Account(2, 50);
42 |
43 | acc1.getLock().lock();
44 |
45 | Thread checkThread = new Thread(() -> {
46 | SimpleTransfer transfer = new SimpleTransfer(acc1, acc2, 10);
47 | boolean result;
48 | try {
49 | result = transfer.call();
50 | assertFalse(result);
51 | } catch (Exception e) {
52 | fail(e.getMessage());
53 | }
54 |
55 | assertEquals(100, acc1.getBalance());
56 | assertEquals(50, acc2.getBalance());
57 | assertEquals(1, acc1.getFailCount());
58 | assertEquals(0, acc2.getFailCount());
59 | });
60 |
61 | try {
62 | checkThread.start();
63 | checkThread.join();
64 | } finally {
65 | acc1.getLock().unlock();
66 | }
67 | }
68 |
69 | @Test
70 | public void testTransferLockedTo() throws Exception {
71 | Account acc1 = new Account(1, 100);
72 | Account acc2 = new Account(2, 50);
73 |
74 | acc2.getLock().lock();
75 |
76 | Thread checkThread = new Thread(() -> {
77 | SimpleTransfer transfer = new SimpleTransfer(acc1, acc2, 10);
78 | boolean result;
79 | try {
80 | result = transfer.call();
81 | assertFalse(result);
82 | } catch (Exception e) {
83 | fail(e.getMessage());
84 | }
85 |
86 | assertEquals(100, acc1.getBalance());
87 | assertEquals(50, acc2.getBalance());
88 | assertEquals(0, acc1.getFailCount());
89 | assertEquals(1, acc2.getFailCount());
90 | });
91 |
92 | try {
93 | checkThread.start();
94 | checkThread.join();
95 | } finally {
96 | acc2.getLock().unlock();
97 | }
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------