cse 219 computer science iii multithreaded issues
TRANSCRIPT
CSE 219Computer Science III
Multithreaded Issues
Java Thread Issues• We’ve used threads naively
• Many things to consider when using multithreading in an application:– Swing & threads– race conditions– locking threads– deadlock
Swing & Threads• NOTE: Swing is not thread-safe
• We have to be careful when mixing GUIs (Swing) and Concurrency (Threads)
• The screen should never “freeze”
• The program should always be responsive to user interaction– no matter what it may currently be doing
Race Conditions• Threads are independent pieces of control operating on
the same data.• One thread could interfere with another thread’s work
– results in corrupted data
– called a race condition
• Common issue in Consumer – Producer relationships– one thread is the producer, adding to a shared data structure
– one thread is the consumer, using a shared data structure
• 2003 Blackout problem? Race Conditions in software:– http://www.securityfocus.com/news/8412
• When do race conditions happen?– when transactions lack atomicity
Atomicity• A property of a transaction
• An atomic transaction happens either completely or not at all
• What’s a transaction?– code execution (ex: method) that changes stored data
• Ex: instance variables, static variables, database
Example: A Corruptible Bank
BuggedTransferer
-bank:BadBank
-fromAccount:int
+$MAX:double
+$DELAY:int
+run():void
BadBank
-account: double[]
+$INIT_BALANCE:double
+$NUM_ACCOUNTS:int
+transfer(from:int,
to:int,
double:amount):void
-getTotalBalance():double
<<interface>>
Runnable
• Assumptions:– We will create a single BadBank and make random transfers, each in separate
threads
100 1
public class BadBank { public static int INIT_BALANCE = 1000, NUM_ACCOUNTS = 100; private double[] accounts = new double[NUM_ACCOUNTS];
public BadBank() {for (int i = 0; i < NUM_ACCOUNTS; i++)
accounts[i] = INIT_BALANCE; }
public void transfer(int from, int to, double amount) {if (accounts[from] < amount) return;accounts[from] -= amount;System.out.print(Thread.currentThread());System.out.printf("%10.2f from %d to %d",amount,from,to);accounts[to] += amount;double total = getTotalBalance();System.out.printf(" Total Balance: %10.2f%n", total);
}
private double getTotalBalance() {double sum = 0;for (double a : accounts) sum += a;return sum;
}} BadBank.java
public class BuggedTransferer implements Runnable {private BadBank bank;private int fromAccount;public static final double MAX = 1000;public static final int DELAY = 100; public BuggedTransferer(BadBank b, int from) { bank = b; fromAccount = from;} public void run() { try { while(true) { int toAccount = (int)(bank.NUM_ACCOUNTS * Math.random()); double amount = MAX * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int)(DELAY*Math.random()));
} } catch(InterruptedException e) {/*SQUELCH*/}
}}
BuggedTransferer.java
public class AtomiclessDriver {
public static void main(String[] args) {
BadBank b = new BadBank();
for (int i = 0; i < BadBank.NUM_ACCOUNTS; i++) {
BuggedTransferer bT = new BuggedTransferer(b,i);
Thread t = new Thread(bT);
t.start();
}
}
}
AtomiclessDriver.java
What results might we get?…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 100000.00…Total Balance: 99431.55…Total Balance: 99367.34…
• Why might we get invalid balance totals?• race conditions• operations on shared
data may lack atomicity
• Bottom line:• a method or even a single statement is not an atomic
operation
• this means that the statement can be interrupted during its operation
A single statement?• Compiled into multiple low-level statements• To see:javap –c –v BadBank• Ex: accounts[from] -= amount;… 21 aload_0 22 getfield #3 <Field double accounts[]> 25 iload_1 26 dup2 27 daload 28 dload_3 29 dsub 30 dastore…
Race Condition Example• Thread 1 & 2 are in transfer at the same time.
Thread 1 Thread 2aload_0getfield #3iload_1dup2daloaddload_3
aload_0getfield #3iload_1dup2daloaddload_3dsubdastore
dsubdastore
What’s the problem?
This might store corrupted data
How do we guarantee atomicity?• By locking methods or code blocks• What is a lock?
– a thread gets a lock on critical shared code
– only one thread may execute locked code at a time
• Two techniques:1. Locking code blocks via the ReentrantLock class
• new to Java in 1.5
2. Synchronizing methods or code blocks
• Synchronization is easier to use, ReentrantLock with Condition classes are more powerful
• Can we lock an object?– yes, we’ll see how in a few moments
ReentrantLock• Basic structure to lock critical code:ReentrantLock myLock = new ReentrantLock();
…
myLock.lock();
try {
// CRITICAL AREA TO BE ATOMICIZED
}
finally {
myLock.unLock();
}
• When a thread enters this code:– if no other lock exists, it will execute the critical code
– otherwise, it will wait until previous locks are unlocked
Updated transfer methodpublic class GoodBank { private ReentrantLock bankLock = new ReentrantLock(); private double[] accounts = new double[NUM_ACCOUNTS];… public void transfer(int from, int to, double amount) {
bankLock.lock();try {
if (accounts[from] < amount) return; accounts[from] -= amount; System.out.print(Thread.currentThread()); System.out.printf("%10.2f from %d to%d",amount,from,to); accounts[to] += amount; double total = getTotalBalance(); System.out.printf(" Total Balance: %10.2f%n", total);
} finally{bankLock.unlock();
} }}
• NOTE: This works because transfer is the only mutator method for accounts– What if there were more than one?
• then we’d have to lock the accounts object
Synchronizing Methodspublic synchronized void deposit(int d) {…}
• A monitor (object with synchronized methods) allows one thread at a time to execute a synchronized method on the object– Locks the method when the synchronized method is invoked
– Only one synchronized method may be active on an object at once
– All other threads attempting to invoke synchronized methods must wait
– When a synchronized method finishes executing, the lock on the object is released and the monitor lets the highest-priority ready thread proceed
• Note: Java also allows synchronized blocks of code. Ex:synchronized (this) {
while (blah blah blah)
blah blah blah
}
Synchronized transfer methodpublic class SynchronizedBank {… public synchronized void transfer
(int from, int to, double amount) { if (accounts[from] < amount) return; accounts[from] -= amount; System.out.print(currentThread()); System.out.printf("%10.2f from %d to%d",
amount,from,to); accounts[to] += amount; double total = getTotalBalance(); System.out.printf(" Total Balance:
%10.2f%n", total);}
}}
Locking Data• synchronized is fine for locking methods, but what
about data?– the synchronized keyword cannot be used on variables– Ex: the account variable
• to lock/unlock data we can use a combination of approaches:– use only synchronized methods for manipulating shared dataOR– define a method for acquiring a lock on data– while an object is locked, make all other threads wanting to
use it wait– define a method for releasing a piece (or pieces) of data,
notifying all other threads when doneOR– declare variables as volatile
public class GoodBank { private double[] accounts = new double[NUM_ACCOUNTS]; private boolean inUse = false;
public synchronized void acquire() throws InterruptedException { while (inUse) wait(); inUse = true; notify(); } public synchronized void release() { inUse = false; notify(); } // ALL METHODS USING accounts MUST GET LOCKS FIRST
Locked accounts object
// SOMEWHERE INSIDE GoodBank classacquire();… // DO LOTS OPERATIONS THAT… // MAY MANIPULATE THE accounts… // OBJECT AS PART OF SOME… // GREATER ALGORITHMrelease();
Using Object Locks
wait and notifyAll• wait() blocks the current thread (potentially forever)
– another thread must notify it– all Objects have a wait method that can be called
public synchronized void acquire() throws InterruptedException { while (inUse) wait(); inUse = true; notify(); }
• notify() & notifyAll() unblock a thread or all threads waiting on the current object– must be called at the end of a synchronized method, else you
may get an IllegalMonitorStateExceptionpublic synchronized void release(Object requestor) {
inUse = false;notifyAll();
}
notify and notifyAll• A thread T1 blocked by wait() is made
runnable when another thread T2 invokes notifyAll() on the object T1 operates on.
• notify() wakes up one of the waiting threads• notifyAll() wakes up all waiting threads
• Once a thread is notified– it attempts to obtain lock again, and if successful it
gets to lock all other threads out
public class GoodBank { private volatile double[] accounts = new double[NUM_ACCOUNTS];
• The volatile keyword guarantees single threaded use• More overhead for JVM, so slower• Mixed results for use
Alternative: volatile
What’s the worst kind of race condition?• One that rarely happens, but causes devastating
effects
• Hard to find, even during extensive testing
• Can be hard to simulate, or deliberately produce– depends on thread scheduler, which we don’t control
• Surface only during catastrophes
• Moral of the story, don’t rely on thread scheduler– make sure your program is thread safe
• should be determined logically, before testing
Dining Philosophers Problem
• 5 philosophers• 5 forks• 5 plates • 1 large bowl of spaghetti in center
Deadlocks and other matters• Deadlock:
– a thread T1 holds a lock on L1 and wants lock L2
AND– a thread T2 holds a lock on L2 and wants lock L1.– problem: a thread holds a lock on one or more
objects when it invoked wait() on itself
• Morals of the story:– don’t let waiting threads lock other data
• release all their locks before going to wait• there are all sorts of proper algorithms for thread lock
ordering (you’ll see if you take CSE 306)
– a thread cannot be resumed if a wait is not matched by a notify