260 likes | 386 Vues
This lecture, led by Paul Krause, delves into the essential concepts of thread safety in software development. Topics covered include locking mechanisms, deadlock situations, race conditions, and starvation. Demonstrations of non-atomic operations, implicit locking, and best practices for synchronizing access methods illustrate how to manage concurrency effectively. Through practical examples, attendees will learn about locking strategies for both objects and classes, enhancing their understanding of thread safety and improving the reliability of multi-threaded applications.
E N D
Internet Software Development 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)