Synchronization 10-28-2013
Synchronization Coming next: Multithreading in JavaFX (javafx.concurrent) Read: Java Tutorial on concurrency JavaFX Tutorial on concurrency Effective Java, Chapter 9 Project#1: due Wed, October 30 th Exam#2 is scheduled for Tues., Nov. 19, 7:00 pm, Snell 213
What is a thread? process running program thread single, sequential flow of control within a program ( lightweight process) What resources do threads share? What is not shared? the threads share the resources & data in the program each thread has its own stack and program counter
Methods for creating a thread: Implement the Runnable Interface (preferred) Extend the Thread class What are thread states? new, runnable (running), blocked (waiting for a lock), waiting (for another thread), timed_waiting, terminated
Note: the JVM scheduler must coordinate with the Operating System From the pool of runnable threads, choose the one with the highest priority If a tie, use round-robin scheduling Each thread gets a time slice When the time slice expires (or the thread stops running), then run the next one The JVM uses preemptive scheduling If a thread enters the runnable state and it has a higher priority than the currently running thread, then switch them (higher priority thread becomes running, other one returns to runnable)
public class X { // note that class X is not Runnable private Thread t; // composition (X hasa thread) public X( ) { // constructor for class X t = new Thread ( new Runnable() { public void run() { /* code that this thread runs */ } // end run method } // end anonymous Runnable class ); // ends the call to Thread s constructor t.start(); // thread t is now ready to run } // end X constructor
Two concurrent threads must be able to execute correctly with *any* interleaving of their instructions Scheduling is not under the control of the application writer Note: instructions means machine instructions, not a line of code in a high level programming language If two threads are operating on completely independent data or the shared data/resources are read-only, then there s no problem If they share data, then application programmer may need to introduce synchronization primitives to safely coordinate their access to the shared data/resources
Suppose we have multiple threads sharing a database of bank account balances. Consider the deposit and withdraw functions int withdraw (int account, int amount) { balance = readbalance(account); balance = balance amount; updatebalance(account, balance); return balance; } int deposit (int account, int amount) { balance = readbalance(account); balance = balance + amount; updatebalance(account, balance); return balance; } What happens if multiple threads execute these functions for the same account at the same time?
Balance starts at $500 and then two processes withdraw $100 at the same time Two people at different ATMs; Update runs on the same back-end computer at the bank int withdraw(int acct, int amount) { balance = readbalance(acct); balance = balance amount; updatebalance(acct,balance); return balance; } int withdraw(int acct, int amount) { balance = readbalance(acct); balance = balance amount; updatebalance(acct,balance); return balance; } What could go wrong? Different Interleavings => Different Final Balances!!!
If the second does readbalance before the first does writebalance. Two examples: balance = readbalance(account); balance = readbalance(account); balance = balance - amount; updatebalance(account, balance); $500 $500 balance = readbalance(account); balance = readbalance(account); balance = balance - amount; updatebalance(account, balance); balance = balance - amount; updatebalance(account, balance); $400 balance = balance - amount; updatebalance(account, balance); Before you get too happy, deposits can be lost just as easily!
Two concurrent threads must be able to execute correctly with *any* interleaving of their instructions Scheduling is not under the control of the application writer Note: instructions means machine instructions, not a line of code in a high level programming language When the correct output depends on the scheduling or relative timings of operations, you call that a race condition. Output is non-deterministic To prevent this we need mechanisms for controlling access to shared resources, i.e. enforce determinism
Synchronization required for all shared data structures like Shared databases (like account balances) Global variables Dynamically allocated structures (off the heap) like queues, lists, trees, etc. What are not shared data structures? Variables that are local to a procedure/method (on the stack) Note, though, that other bad things happen if try to share pointer to a variable that is local to a procedure
Have an array of accounts (simulating the bank) Have several threads, where each thread transfers some money from 1 randomly chosen account to another Periodically, test to see if the bank still has the same amount of money in the accounts public void transfer(int from, int to, int amount) { if (accounts[from] < amount) return; accounts[from] -= amount; accounts[to] += amount; ntransacts++; if (ntransacts % NTEST == 0) test(); }
public class UnsynchBankTest { public static void main(string[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); int i; for (i = 0; i < NACCOUNTS; i++) { TransferThread t = new TransferThread(b, i, INITIAL_BALANCE); t.setpriority(thread.norm_priority + i % 2); t.start(); } }
public class Bank { public Bank(int n, int initialbalance) { accounts = new int[n]; for (int i = 0; i < accounts.length; i++) accounts[i] = initialbalance; ntransacts = 0; } public void transfer(int from, int to, int amount) { if (accounts[from] < amount) return; accounts[from] -= amount; accounts[to] += amount; ntransacts++; if (ntransacts % NTEST == 0) test(); }
accounts[to] += amount; // is not an atomic operation load accounts[to] in some register add amount to that register save the register in accounts[to]
thread t1, amount = 500 thread t2, amount = 1000 both attempting to add their amount to accounts[7] trace done in class many scenarios, and several may be OK but many will not be OK solution: synchronized
All objects in Java have a lock associated with it Bank boa = new Bank(10, 10000); The object boa refers to a bank with 10 accounts each initially containing $10,000 There is a lock associated with that object NOTE: the lock is associated with the object, not with code
when a thread enters a synchronized method (or block of code), the object becomes locked if another thread attempts to enter any synchronized method for the same object, it becomes blocked when the thread who holds the lock exits the synchronized method, it unlocks the object periodically, the thread scheduler activates the threads that are waiting for the lock; the 1 st one to run will check for the lock; if it is unlocked, that thread enters; otherwise it blocks itself again
interrupt(), isinterrupted(), interrupted() avoid the deprecated methods stop(), resume() Methods that are inherited from Object: wait() the running thread must own this object s monitor (key); it causes the running thread to block and give up the key until another thread calls notify() or notifyall() notify() the running thread must own this object s monitor (key); it wakes up exactly one of the threads that are waiting on the object notifyall() similar to the above, but wakes up all waiting thread on that object
public synchronized void transfer(int from, int to, int amount) { try { while (accounts[from] < amount) wait(); accounts[from] -= amount; accounts[to] += amount; ntransacts++; notifyall(); if (ntransacts % NTEST == 0) test(); } catch(interruptedexception e) { // handle the exception } }