260 likes | 274 Views
Component-Based Software Engineering. Understanding Thread Safety Paul Krause. Lecture 9. Contents A bit more on Locks Deadlock Race conditions Starvation Non-atomic operations. Locks. Every object has a lock You lock an object by synchronizing on it
E N D
Component-Based Software Engineering Understanding Thread Safety Paul Krause
Lecture 9 Contents • A bit more on Locks • Deadlock • Race conditions • Starvation • Non-atomic operations
Locks • Every object has a lock • You lock an object by synchronizing on it public void addElement(Object item) { synchroniized(myArrayList) { // do the stuff } } public boolean elementExists(Object item) { return myArrayList.contains(item) } • To be absolutely accurate, synchronize on access methods too
What to Lock? public class McBurgerPlace { private static Person ceo = new Person(); private Object floor; private Object sodaFountain; public static synchronized Person receiveCeo() { return ceo; } public synchronized void waxFloor() {// do stuff … } public Object getSodaFountain() { synchronized(sodaFountain) { // do stuff … } } }
Non-implicit locking • If threadA is receiving the ceo, can threadB wax the floor? • If threadB is waxing the floor, can threadC get the soda fountain? • The answer is Yes in both cases • The JVM doesn’t do nested locking
Locking objects not members public class LockObjectNotMemberVariables { private List myList = new ArrayList(); public static void main(String[] args) { LockObjectNotMemberVariables lonmv = new LockObjectNotMemberVariables(); lonmv.lockTest(); } public synchronized void lockTest() { System.out.println("Is THIS object locked? " + Thread.holdsLock(this)); System.out.println("Is the list object locked? " + Thread.holdsLock(myList)); } }
Locking objects not members init: deps-jar: Created dir: /Users/paulkrause/Java/threading/build/classes Compiling 1 source file to /Users/paulkrause/Java/threading/build/classes compile-single: run-single: Is THIS object locked? true Is the list object locked? false BUILD SUCCESSFUL (total time: 4 seconds)
Locking classes not instances public class ClassLockNotObjectLock { public static void main(String[] args) { lockTest(); } public static synchronized void lockTest() { ClassLockNotObjectLock clnol = new ClassLockNotObjectLock(); System.out.println("Is the Class locked? " + Thread.holdsLock(clnol.getClass())); System.out.println("Is the object instance locked? " + Thread.holdsLock(clnol)); } }
Locking classes not instances init: deps-jar: Compiling 1 source file to /Users/paulkrause/Java/threading/build/classes compile-single: run-single: Is the Class locked? true Is the object instance locked? false BUILD SUCCESSFUL (total time: 4 seconds)
What to lock? • Synchronising on Objects other than “this” provides more concurrency • But controlling locks on such a fine scale can be inefficient if a method needs to access several objects • The safest general policy is not to allow unsynchronized access to the resources you need to lock and synchronize methods
Thread Safety Examples • Deadlock • First thread gets Lock 1 and then tries to get Lock 2 • Second thread gets Lock 2 and then tries to get Lock 1 • Race Conditions • Two threads race for a common object • Starvation • A thread is never/rarely allowed to execute
Deadlock public void run() { String name = Thread.currentThread().getName(); synchronized(lockA) { System.out.println(name + ": locked" + lockA); delay(name); System.out.println(name + ": trying to get " + lockB); synchronized(lockB) { System.out.println(name + ": locked" + lockB); } } }
Example run init: deps-jar: Compiling 1 source file to /Users/paulkrause/Java/threading/build/classes compile-single: run-single: Thread-0: lockedLock 1 Thread-0: delaying 1 second Thread-1: lockedLock 2 Thread-1: delaying 1 second Thread-0: trying to get Lock 2 Thread-1: trying to get Lock 1 Both threads now blocked
Race condition example if (Math.random() > .5) { peter.start(); paul.start(); } else { paul.start(); peter.start(); }
Race condition example public void run() { System.out.println(getName() + ": trying for lock on " + server); synchronized(server) { System.out.println(getName() + ": has lock on " + server); // wait 2 seconds: show the other thread is really // blocked try { Thread.sleep(2000); } catch (InterruptedException ie) { ie.printStackTrace(); } System.out.println(getName() + ": releasing lock "); }
Example run (I) run-single: Peter: trying for lock on the common object Peter: has lock on the common object Paul: trying for lock on the common object Peter: releasing lock Paul: has lock on the common object Paul: releasing lock BUILD SUCCESSFUL (total time: 5 seconds)
Example run (II) run-single: Paul: trying for lock on the common object Paul: has lock on the common object Peter: trying for lock on the common object Paul: releasing lock Peter: has lock on the common object Peter: releasing lock BUILD SUCCESSFUL (total time: 8 seconds)
Starvation Example for (int i = 0; i < 4; i++) { // create a runner Runner r = new Runner(); r.setPriority(Thread.MAX_PRIORITY); // set the first thread to starve if (i == 0) { r.setPriority(Thread.MIN_PRIORITY); r.setName("Starvation Thread"); } // start the thread r.start(); r.yield(); // optional line }
run-single: Starvation Thread: is working 10 Thread-1: is working 10 Thread-2: is working 10 Thread-1: is working 9 Thread-2: is working 9 Thread-1: is working 8 Thread-2: is working 8 Thread-1: is working 7 Thread-2: is working 7 Thread-1: is working 6 Thread-2: is working 6 Thread-1: is working 5 Thread-2: is working 5 Thread-1: is working 4 Thread-2: is working 4 Thread-1: is working 3 Thread-2: is working 3 Thread-1: is working 2 Thread-2: is working 2 Thread-1: is working 1 Thread-2: is working 1 Thread-3: is working 10 Thread-3: is working 9 Thread-3: is working 8 Thread-3: is working 7 Thread-3: is working 6 Thread-3: is working 5 Thread-3: is working 4 Thread-3: is working 3 Thread-3: is working 2 Thread-3: is working 1 Starvation Thread: is working 9 Starvation Thread: is working 8 Starvation Thread: is working 7 Starvation Thread: is working 6 BUILD SUCCESSFUL (total time: 1 second) Example run
Atomic Operations • Synchronization blocks some code as “atomic” • A thread will not be swapped out in the middle of an atomic operation • Be careful in assuming any statements are atomic!
Non-atomic operations • x = 45; • Is atomic if x is an int • Is not atomic if x is double or long • One operation for the high 32 bits, one for the low 32 bits
Non-atomic operations • Treat x = 7; y = x++; • As x = 7; int temp = x + 1; x = temp; y = x; A thread swap here could lead to unexpected results
Class NonAtomic.java • Class has a static variable • static int x; • The start ten threads that each do the following: for (int i= 0; i < 10; i++) { int reference = (int) (Math.random() * 100); x = reference; // some calculation to make a slight delay if (x == reference) { validCounts++; } else { invalidCounts++; } }
Example run run-single: Thread-1 valid: 10 invalid: 0 Thread-6 valid: 10 invalid: 0 Thread-0 valid: 9 invalid: 1 Thread-3 valid: 7 invalid: 3 Thread-7 valid: 8 invalid: 2 Thread-8 valid: 7 invalid: 3 Thread-4 valid: 5 invalid: 5 Thread-9 valid: 8 invalid: 2 Thread-2 valid: 6 invalid: 4 Thread-5 valid: 6 invalid: 4 BUILD SUCCESSFUL (total time: 3 seconds)
Problem fixed for (int i= 0; i < 10; i++) { synchronized(NonAtomic.class) { int reference = (int) (Math.random() * 100); x = reference; // Do something intensive here if (x == reference) { validCounts++; } else { invalidCounts++; } }
New run run-single: Thread-0 valid: 10 invalid: 0 Thread-7 valid: 10 invalid: 0 Thread-2 valid: 10 invalid: 0 Thread-1 valid: 10 invalid: 0 Thread-5 valid: 10 invalid: 0 Thread-3 valid: 10 invalid: 0 Thread-8 valid: 10 invalid: 0 Thread-6 valid: 10 invalid: 0 Thread-9 valid: 10 invalid: 0 Thread-4 valid: 10 invalid: 0 BUILD SUCCESSFUL (total time: 3 seconds)