380 likes | 533 Views
Threading and Concurrent Programming in Java. Synchronization D.W. Denbo. Outline. Race condition Lock objects synchronized keyword Synchronized block Volatile Fields Deadlocks Lock testing. Race Condition.
E N D
Threading and ConcurrentProgramming in Java Synchronization D.W. Denbo Java Threads
Outline • Race condition • Lock objects • synchronized keyword • Synchronized block • Volatile Fields • Deadlocks • Lock testing Java Threads
Race Condition In most real applications, two or more threads will need to share access to the same objects. What happens if two threads have access to the same object calls a methods that changes the object state? As you might expect the threads can step on each other’s toes. Such a situation is often called a race condition. Java Threads
Example of a Race Condition To avoid corruption of share data, you must learn how to synchronize the access. In this example, we have the class Bank with the method transfer. Money can be transferred between accounts. A thread is created for each account to transfer money from its account to another. Java Threads
public void transfer(int from, int to, double amount) { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(“%10.2f from %d to $d”,amount,from,to); accounts[to]+= amount; System.out.printf(“Total: %10.2f%n”,getTotalBalance()); } Java Threads
public void run() { try { int toAccount = (int)(bank.size()*Math.random()); double amount = maxAmount*Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int)(DELAY*Math.random())); } catch (InterruptedException ie) { } } Java Threads
UnsynchBankTest Java Threads
While this simulation is running, we don’t know how much money is in any one account. We do, however, know that the total amount of money should remain constant! Java Threads
What Happened? Errors have crept in and some amount of money was either lost or spontaneously created. Why? accounts[to] += amount; The problem is that these are not atomic operations! Java Threads
The instructions might be processed as follows: • Load accounts[to] into a register • Add amount • Move the result back to accounts[to]. If the first thread executes Steps 1 & 2, then is interrupted by a second thread that updates the same entry, the second threads changes are lost. Java Threads
Thread 2 accounts[to] Thread 1 Thread 1 register Thread 2 register 5000 5000 load 5500 add 5000 5000 5000 load 5900 5000 add 5900 5900 store 5500 5500 store
Lock Objects Starting with JDK 5.0, there are two methods for protecting a code block from concurrent access. • The synchronized keyword • The ReentrantLock class We’ll first take a closer look at the ReentrantLock: Java Threads
This construct guarantees that only one thread can access the critical section at a time. myLock.lock(); // a ReentrantLock object try { // critical section } finally { myLock.unlock(); // make sure lock is unlocked } Java Threads
public class Bank { public void transfer(int from, int to, double amount) { bankLock.lock(); try { if(accounts[from] < amount) return; System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(“%10.2f from %d to %d”,amount,from,to); accounts[to] += amount; System.out.printf(“Total: %10.2f%n”,getTotalBalance()); } finally { bankLock.unlock(); } } .... // Reentrant lock implements the Lock interface private Lock bankLock = new ReentrantLock(); } Java Threads
Note that • Each Bank object has its own ReentrankLock • If two threads try to access the same Bank object, then the lock will serialize access. • If two threads try to access different Bank objects, each thread acquires a different lock and neither thread is blocked. Java Threads
Condition Objects Often, a thread enters a critical section, only to discover that it can’t proceed until a condition is fulfilled. You use a conditionobject to manage threads that have acquired lock but cannot do useful work. Lets extend our example to cover the case of insufficient funds. if(bank.getBalance(from) >= amount) bank.transfer(from, to, amount); The above won’t work! Java Threads
So what do we do when there isn’t enough money? We wait until some other thread puts enough money in! public void transfer(int from, int to, double amount) { bankLock.lock(); try { while(accounts[from] < amount) { // wait } // transfer funds } finally { bankLock.unlock(); } } Java Threads
A lock object can have one or more associated condition objects. Here we set up a condition object to represent the “sufficient funds” condition. class Bank { public Bank() { .... sufficientFunds = bankLock.newCondition(); } .... private Condition sufficientFunds; } Java Threads
public void transfer(int from, int to, double amount) { bankLock.lock(); try { while(accounts[from] < amount) sufficientFunds.await(); // transfer funds ... sufficientFunds.signalAll(); } finally { bankLock.unlock(); } } NOTE: the call to signalAll does not immediately activate a waiting thread. It only unblocks the waiting threads so that they can compete for entry into the object. Java Threads
SynchBankTest Java Threads
A lock protects sections of code, allowing only one thread access at a time. • A lock manages threads that are trying to enter protected code segments. • A lock can have one more associated condition objects. • Each condition object manages threads that have entered a protected code section but cannot proceed. Java Threads
synchronized Keyword Every object in Java has an implicit lock. If a method is declared with the synchronized keyword, then the object’s lock protects the entire method. This implicit lock has a single implicit condition. The wait method adds a thread to the wait set, and the notifyAll/notify methods unblock the waiting threads. Java Threads
class Bank { public synchronized void transfer(int from, int to, double amount) { while(accounts[from] < amount) wait(); // wait on objects lock’s single condition accounts[from] -= amount; accounts[to] += amount; notifyAll(); // notify all threads waiting on the condition } public synchronized double getTotalBalance() {....} private double accounts[]; } Java Threads
While using synchronized yields more concise code, implicit locks and conditions have some limitations. • Cannot interrupt a thread that is trying to acquire a lock • Cannot specify a timeout when trying to acquire a lock • Having a single condition per lock can be inefficient • The virtual machine locking primitives do not map well to the most efficient locking mechanisms available in hardware. Java Threads
Core Java authors recommendations • Best to use neither Lock/Condition or the synchronized keyword. Should first see if java.util.concurrent package offers a solution. • If the synchronized keyword works for your solution, you should use it. You write less code and have less room for error. • Use Lock/Condition if you specifically need the additional power that these constructs give you. Java Threads
Synchronized Blocks If you deal with legacy code (or legacy programmers :-), you need to know something about the built-in synchronization primitives. Recall each object has a lock. class Bank { public void transfer(int from, int to, double amount) { synchronized(lock) { // an ad-hoc lock accounts[from] -= amount; accounts[to] += amount; } } private double accounts[]; private Object lock = new Object(); } Java Threads
Volatile Fields • Computers with multiple processors can temporarily hold memory values in registers or local memory caches. Threads running in different processors may see different values for the same memory location! • Compilers can reorder instructions for maximum throughput. Compilers won’t change the meaning of the code, but will assume that all changes occur within the block of optimized code. However, a memory value can be changed by another thread! Java Threads
Concurrent access to a field is safe in these three conditions: • The field is volatile • The field is final, and it is accessed after the constructor has completed. • The field access is protected by a lock • NOTE: Prior to JDK 5.0, the semantics of volatile were rather permissive. Java Threads
Deadlocks • Locks and conditions cannot solve all problems that might arise in multithreading. Consider ... • Account 1: $200 • Account 2: $300 • Thread 1: Transfer $300 from Account 1 to 2 • Thread 2: Transfer $400 from Account 2 to 1. • In the example, deadlocks can’t occur because each transfer is limited to $1000. With 100 acounts and $100,000 total, at least on account must have $1000. What if we change this restriction? Java Threads
SynchBankTest_dl Java Threads
Creating Deadlocks • You can create a deadlock by making the i’th thread responsible for putting money into the i’th account, rather than for taking it out of the i’th account. It becomes possible for all the threads to gang up on one account, each trying to remove more money that it contains. • Another deadlock situation: Change the signalAll method to signal in the SynchBankTest program. It deadlocks because it is now only unblocking a single thread, if that thread can’t proceed... • Unfortunately, there is nothing in the Java language to avoid or break these deadlocks. Java Threads
Lock Testing and Timeouts • A thread blocks indefinitely when it calls the lock method to acquire a lock that is owned by another thread. Other things you can try: • tryLock boolean method. Returns true if lock acquired. • tryLock(100, TimeUnit.MILLISECONDS), with a timeout. tryLock can be unfair and grab the lock without waiting its turn, with the timeout tryLock is fair. • myCondition.await(1000, TimeUnit.MICROSECONDS), will return if notifyAll has been called by another thread, or if the timeout has elapsed. Java Threads
Read/Write Locks • java.util.concurrent.locks package defines two lock classes, ReentrantLock and ReentrantReadWriteLock. The latter is advantageous if many threads read and only a few write. Java Threads
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock(); public double getTotalBalance() { readLock.lock(); try { .... } finally { readLock.unlock(); } } public void transfer(...) { writeLock.lock(); try { ... } finally { writeLock.unlock(); } } Java Threads
Why the stop and suspend Methods are Deprecated • JDK 1.0 defined a stop method that simply terminates a thread, and a suspend method that blocks a thread. • Both of these methods have been deprecated since JDK 1.2. The stop method is inherently unsafe, and experience has shown that the suspend method can lead to deadlocks. Java Threads
stop method This method terminates all pending methods, including the run method. When a thread is stopped, it immediately gives up the locks on all objects that it has locked. This can leave objects in an inconsistent state. For example, suppose a TransferThread is stopped in the middle of moving money from one account to another, after the withdrawal and before the deposit? Now the Bank object is damaged. Java Threads
suspend Method Unlike stop, suspend won’t damage objects. However, if you suspend a thread that owns a lock, then the lock is unavailable until the thread is resumed. If the thread that calls the suspend method tries to acquire the same lock, the program deadlocks. This situation occurs frequently in GUIs. If you want to suspend a thread, introduce a variable and test it in a safe place of your run method. Java Threads