├── .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 | --------------------------------------------------------------------------------