Principles of Software Construction: Objects, Design, and ... · Principles of Software...
Transcript of Principles of Software Construction: Objects, Design, and ... · Principles of Software...
115-214
SchoolofComputerScience
PrinciplesofSoftwareConstruction:Objects,Design,andConcurrency
Concurrency:Safety
ChristianKästner BogdanVasilescu
215-214
315-214
Example:Money-Grabpublic class BankAccount {
private long balance;
public BankAccount(long balance) {this.balance = balance;
}
static void transferFrom(BankAccount source,BankAccount dest, long amount) {
source.balance -= amount;dest.balance += amount;
}
public long balance() {return balance;
}}
415-214
Whatwouldyouexpectthistoprint?public static void main(String[] args) throws InterruptedException
{BankAccount bugs = new BankAccount(100);BankAccount daffy = new BankAccount(100);
Thread bugsThread = new Thread(()-> {for (int i = 0; i < 1000000; i++)
transferFrom(daffy, bugs, 100);});
Thread daffyThread = new Thread(()-> {for (int i = 0; i < 1000000; i++)
transferFrom(bugs, daffy, 100);});
bugsThread.start(); daffyThread.start();bugsThread.join(); daffyThread.join();System.out.println(bugs.balance() + daffy.balance());
}
515-214
Whatwentwrong?
• Daffy&Bugsthreadswerestompingeachother
• Transfersdidnothappeninsequence• Constituentreadsandwritesinterleavedrandomly
• Randomresultsensued
615-214
Fix:Synchronizedaccess(visibility)@ThreadSafepublic class BankAccount {
@GuardedBy(“this”)private long balance;
public BankAccount(long balance) {this.balance = balance;
}
static synchronized void transferFrom(BankAccount source,BankAccount dest, long amount) {
source.balance -= amount;dest.balance += amount;
}
public synchronized long balance() {return balance;
}}
715-214
Example:serialnumbergenerationWhatwouldyouexpectthistoprint?public class SerialNumber {
private static long nextSerialNumber = 0;
public static long generateSerialNumber() {return nextSerialNumber++;
}
public static void main(String[] args) throws InterruptedException {Thread threads[] = new Thread[5];for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {for (int j = 0; j < 1_000_000; j++)
generateSerialNumber();});threads[i].start();
}for(Thread thread : threads) thread.join();System.out.println(generateSerialNumber());
}}
815-214
Whatwentwrong?
• The++(increment)operatorisnotatomic!– Itreadsafield,incrementsvalue,andwritesitback
• IfmultiplecallstogenerateSerialNumberseethesamevalue,theygenerateduplicates
915-214
Fix:Synchronizedaccess(atomicity)@ThreadSafepublic class SerialNumber {
@GuardedBy(“this”)private static int nextSerialNumber = 0;
public static synchronized int generateSerialNumber() {return nextSerialNumber++;
}
public static void main(String[] args) throws InterruptedException{Thread threads[] = new Thread[5];for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {for (int j = 0; j < 1_000_000; j++)
generateSerialNumber();});threads[i].start();
}for(Thread thread : threads) thread.join();System.out.println(generateSerialNumber());
}}
1015-214
Part1:DesignataClassLevel
DesignforChange:InformationHiding,
Contracts,DesignPatterns,UnitTesting
DesignforReuse:Inheritance,Delegation,
Immutability,LSP,DesignPatterns
Part2:Designing(Sub)systems
UnderstandingtheProblem
ResponsibilityAssignment,DesignPatterns,GUIvsCore,
DesignCaseStudies
TestingSubsystems
DesignforReuseatScale:FrameworksandAPIs
Part3:DesigningConcurrent
Systems
ConcurrencyPrimitives,Synchronization
DesigningAbstractionsforConcurrency
DistributedSystemsinaNutshell
IntrotoJava
Git,CIStaticAnalysis
GUIsUML MoreGit
GUIsPerformance
Design
1115-214
LearningGoals• UnderstandanduseJavaprimitivesforconcurrency:threads,synchronization,volatile,wait/notify
• Understandproblemsofundersynchronizationandoversynchronization
• Useinformationhidingtoreduceneedforsynchronization
• Decideonstrategytoachievesafety,whenandhowtosynchronize,andusebothfind-grainedandcoarse-grainedsynchronizationasappropriate
1215-214
JAVAPRIMITIVES:WAIT,NOTIFY,ANDTERMINATION
1315-214
GuardedMethods
• Whattodoonamethodifthepreconditionisnotfulfilled(e.g.,transfermoneyfrombankaccountwithinsufficientfunds)• throwexception(balking)• waituntilpreconditionisfulfilled(guardedsuspension)
• waitandtimeout(combination ofbalkingandguardedsuspension)
1415-214
Example:Balking• Iftherearemultiplecallstothejobmethod,onlyonewill
proceedwhiletheothercallswillreturnwithnothing.
public class BalkingExample {private boolean jobInProgress = false;
public void job() {synchronized (this) {
if (jobInProgress) { return; }jobInProgress = true;
}// Code to execute job goes here
}
void jobCompleted() {synchronized (this) {
jobInProgress = false;}
}}
1515-214
GuardedSuspension
• Blockexecutionuntilagivenconditionistrue• Forexample,– pullelementfromqueue,butwaitonanemptyqueue
– transfermoneyfrombankaccountassoonsufficientfundsarethere
• Blockingas(oftensimpler)alternativetocallback
1615-214
MonitorMechanicsinJava
• Object.wait()– suspendsthecurrentthread’sexecution,releasinglocks
• Object.wait(timeout)– suspendsthecurrentthread’sexecutionforuptotimeoutmilliseconds
• Object.notify()– resumesoneofthewaitingthreads
• Seedocumentationforexactsemantics
1715-214
Example:GuardedSuspension
public void guardedJoy() {// Simple loop guard. Wastes// processor time. Don't do this!while (!joy) {}System.out.println("Joy has been achieved!");
}
• Loopuntilconditionissatisfied– wasteful,sinceitexecutescontinuouslywhilewaiting
1815-214
public synchronized guardedJoy() { while(!joy) {
try {wait();
} catch (InterruptedException e) {} }System.out.println("Joy and efficiency have been achieved!");
}
public synchronized notifyJoy() { joy = true;notifyAll();
}
Example:GuardedSuspension• Moreefficient:invokeObject.wait tosuspendcurrentthread
• Whenwaitisinvoked,thethreadreleasesthelockandsuspendsexecution.Theinvocationofwaitdoesnotreturnuntilanotherthreadhasissuedanotification
1915-214
Never invokewaitoutsidealoop!
• Looptestsconditionbeforeandafterwaiting
• Testbeforeskipswait ifconditionalreadyholds– Necessarytoensureliveness– Withoutit,threadcanwaitforever!
• Testingafterwait ensuressafety– Conditionmaynotbetruewhenthreadwakens– Ifthreadproceedswithaction,itcandestroyinvariants!
2015-214
All ofyourwaitsshouldlooklikethissynchronized (obj) {
while (<condition does not hold>) {obj.wait();
}
... // Perform action appropriate to condition}
2115-214
Whycanathreadwakefromawaitwhenconditiondoesnothold?• Anotherthreadcanslipinbetweennotify&wake
• Anotherthreadcaninvokenotify accidentallyormaliciouslywhenconditiondoesnothold– Thisisaflawinjavalockingdesign!– Canworkaroundflawbyusingprivatelockobject
• Notifier canbeliberalinwakingthreads– UsingnotifyAll isgoodpractice,butcausesthis
• Waitingthreadcanwakeupwithoutanotify(!)– Knownasaspuriouswakeup
2215-214
GuardedSuspensionvsBalking
• Guardedsuspension:– Typicallyonlywhenyouknowthatamethodcallwillbesuspendedforafiniteandreasonableperiodoftime
– Ifsuspendedfortoolong,theoverallprogramwillslowdown
• Balking:– Typicallyonlywhenyouknowthatthemethodcallsuspensionwillbeindefiniteorforanunacceptablylongperiod
2315-214
MonitorExampleclass SimpleBoundedCounter {
protected long count = MIN;public synchronized long count() { return count; }
public synchronized void inc() throws InterruptedException {awaitUnderMax(); setCount(count + 1); }
public synchronized void dec() throws InterruptedException {awaitOverMin(); setCount(count - 1); }
protected void setCount(long newValue) { // PRE: lock heldcount = newValue;notifyAll(); // wake up any thread depending on new value }
protected void awaitUnderMax() throws InterruptedException {while (count == MAX) wait(); }
protected void awaitOverMin() throws InterruptedException {while (count == MIN) wait(); }
}
2415-214
Interruption
• Difficulttokillthreadsoncestarted,butmaypolitelyasktostop(thread.interrupt())
• Long-runningthreadsshouldregularlycheckwhethertheyhavebeeninterrupted
• Threadswaitingwithwait() throwexceptionsifinterrupted
• Readdocumentation
public class Thread {public void interrupt() { ... }public boolean isInterrupted() { ... }...
}
2515-214
InterruptionExample
For details, see Java Concurrency In Practice, Chapter 7
class PrimeProducer extends Thread {private final BlockingQueue<BigInteger> queue;PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;}public void run() {
try {BigInteger p = BigInteger.ONE;while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());} catch (InterruptedException consumed) {/* Allow thread to exit */
}}public void cancel() { interrupt(); }
}
2615-214
BUILDINGHIGHERLEVELCONCURRENCYMECHANISMS
2715-214
BeyondJavaPrimitives
• JavaPrimitives(synchronized,wait,notify)arelowlevelmechanisms
• Formosttasksbetterhigher-levelabstractionsexist
• Writingownabstractionsispossible,butpotentiallydangerous– uselibrarieswrittenbyexperts
2815-214
Example:read-writelocks(API)Alsoknownasshared/exclusivemodelocks
private final RwLock lock = new RwLock();
lock.readLock();try {
// Do stuff that requires read (shared) lock} finally {
lock.unlock();}
lock.writeLock();try {
// Do stuff that requires write (exclusive) lock} finally {
lock.unlock();}
• Ifmultiplethreadsareaccessinganobjectforreadingdata,noneedtouseasynchronized block(orothermutuallyexclusivelocks)
2915-214
Example:read-writelocks(Impl.1/2)
public class RwLock {// State fields are protected by RwLock's intrinsic lock
/** Num threads holding lock for read. */@GuardedBy(“this”)
private int numReaders = 0;
/** Whether lock is held for write. */@GuardedBy(“this”)
private boolean writeLocked = false;
public synchronized void readLock() throws InterruptedException { while (writeLocked) {
wait();}numReaders++;
}
3015-214
Example:read-writelocks(Impl.2/2)
public synchronized void writeLock() throws InterruptedException {while (numReaders != 0 || writeLocked) {
wait();}writeLocked = true;
}
public synchronized void unlock() {if (numReaders > 0) {
numReaders--;} else if (writeLocked) {
writeLocked = false;} else {
throw new IllegalStateException("Lock not held");}notifyAll(); // Wake any waiters
}}
3115-214
Caveat:RwLock isjustatoy!
• Ithaspoorfairnessproperties– Readerscanstarvewriters!
• java.util.concurrent providesanindustrialstrengthReadWriteLock
• Moregenerally,avoidwait/notify– Intheearlydaysitwasallyouhad– Nowadays,higherlevelconcurrencyutils arebetter
3215-214
Summary
• Concurrencyforexploitingmultipleprocessors,simplifyingmodeling,simplifyingasynchronousevents
• Safety,livenessandperformancehazardsmatter• SynchronizationonanyJavaobject;volatileensuresvisibility
• Wait/notifyforguards,interruptionforcancelation– buildingblocksforhigherlevelabstractions
3315-214
THREADSAFETY:DESIGNTRADEOFFS
3415-214
Recall:SynchronizationforSafety
• Ifmultiplethreadsaccessthesamemutablestatevariablewithoutappropriatesynchronization,theprogramisbroken.
• Therearethreewaystofixit:– Don'tsharethestatevariableacrossthreads;– Makethestatevariableimmutable;or– Usesynchronizationwheneveraccessingthestatevariable.
3515-214
ThreadConfinement
• Ensurevariablesarenotsharedacrossthreads(concurrencyversionofencapsulation)
• Stackconfinement:– Objectonlyreachablethroughlocalvariables(neverleavesmethod)à accessibleonlybyonethread
– Primitivelocalvariablesalwaysthread-local
• Confinementacrossmethods/inclassesneedstobedonecarefully(seeimmutability)
3615-214
Example:ThreadConfinement
public int loadTheArk(Collection<Animal> candidates) {SortedSet<Animal> animals;int numPairs = 0;Animal candidate = null;// animals confined to method, don't let them escape!animals = new TreeSet<Animal>(new SpeciesGenderComparator());animals.addAll(candidates);for (Animal a : animals) {
if (candidate == null || !candidate.isPotentialMate(a))candidate = a;
else {ark.load(new AnimalPair(candidate, a));++numPairs;candidate = null;
}}return numPairs;
}
• Sharedarkobject• TreeSet isnotthreadsafebutit’slocalà can’tleak• DefensivecopyingonAnimalPair
3715-214
ConfinementwithThreadLocal
• ThreadLocal holdsaseparatevalueforeachcache(essentiallyMap<Thread,T>)– createvariablesthatcanonlybereadandwrittenbythesamethread
– iftwothreadsareexecutingthesamecode,andthecodehasareferencetoaThreadLocalvariable,thenthetwothreadscannotseeeachother'sThreadLocal variables
3815-214
Example:ThreadLocalpublic static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Overridepublic void run() {
threadLocal.set((int) (Math.random() * 100D));
System.out.println(threadLocal.get());}
}
public static void main(String[] args) throws InterruptedException {MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();thread2.start();
thread1.join(); // wait for thread 1 to terminatethread2.join(); // wait for thread 2 to terminate
}
From: http://tutorials.jenkov.com/java-concurrency/threadlocal.html
3915-214
ImmutableObjects
• Immutableobjectscanbesharedfreely• Remember:– Fieldsinitializedinconstructor– Fieldsfinal– Defensivecopyingifmutableobjectsusedinternally
4015-214
Synchronization
• Thread-safe objectsvsguarded:– Thread-safeobjectsperformsynchronizationinternally(clientscanalwayscallsafely)
– Guardedobjectsrequireclientstoacquirelockforsafecalls
• Thread-safeobjectsareidiot-prooftouse,butguardedobjectscanbemoreflexible
4115-214
DesigningThread-SafeObjects
• Identifyvariablesthatrepresenttheobject’sstate– maybedistributedacrossmultipleobjects
• Identifyinvariantsthatconstraintthestatevariables– importanttounderstandinvariantstoensureatomicityofoperations
• Establishapolicyformanagingconcurrentaccesstostate
4215-214
Whatwouldyouchangehere?@ThreadSafepublic class PersonSet {
@GuardedBy("this")private final Set<Person> mySet = new HashSet<Person>();
@GuardedBy("this")private Person last = null;
public synchronized void addPerson(Person p) {mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {return mySet.contains(p);
}
public synchronized void setLast(Person p) {this.last = p;
}}
4315-214
Coarse-GrainedThread-Safety• Synchronizeallaccesstoallstatewiththeobject
@ThreadSafepublic class PersonSet {
@GuardedBy("this")private final Set<Person> mySet = new HashSet<Person>();
@GuardedBy("this")private Person last = null;
public synchronized void addPerson(Person p) {mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {return mySet.contains(p);
}
public synchronized void setLast(Person p) {this.last = p;
}}
4415-214
Fine-GrainedThread-Safety• “Locksplitting”:Separatestateintoindependentregionswith
differentlocks@ThreadSafepublic class PersonSet {
@GuardedBy(“myset")private final Set<Person> mySet = new HashSet<Person>();
@GuardedBy("this")private Person last = null;
public void addPerson(Person p) {synchronized (mySet) {
mySet.add(p);}
}
public boolean containsPerson(Person p) {synchronized (mySet) {
return mySet.contains(p);}
}
public synchronized void setLast(Person p) {this.last = p;
}}
4515-214
PrivateLocks• Anyobjectcanserveaslock
@ThreadSafepublic class PersonSet {
@GuardedBy(“myset")private final Set<Person> mySet = new HashSet<Person>();
private final Object myLock = new Object();@GuardedBy(“myLock")private Person last = null;
public void addPerson(Person p) {synchronized (mySet) {
mySet.add(p);}
}
public synchronized boolean containsPerson(Person p) {synchronized (mySet) {
return mySet.contains(p);}
}
public void setLast(Person p) {synchronized (myLock) {
this.last = p;}
}}
4615-214
Delegatingthread-safetytowelldesignedclasses• RecallpreviousCountingFactorizer
@NotThreadSafepublic class CountingFactorizer implements Servlet {
private long count = 0;
public long getCount() { return count; }
public void service(ServletRequest req, ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);++count;encodeIntoResponse(resp, factors);
}}
4715-214
Delegatingthread-safetytowelldesignedclasses• ReplacelongcounterwithanAtomicLong
@ThreadSafepublic class CountingFactorizer implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() { return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);count.incrementAndGet();encodeIntoResponse(resp, factors);
}}
4815-214
Synchronizeonlyrelevantmethodparts
• Designheuristic:– Getin,getdone,andgetout• Obtainlock• Examineshareddata• Transformasnecessary• Droplock
– Ifyoumustdosomethingslow,moveitoutsidesynchronizedregion
4915-214
Example:Whattosynchronize?
@ThreadSafepublic class AttributeStore {
@GuardedBy("this")private final Map<String, String>
attributes = new HashMap<String, String>();
public synchronized boolean userLocationMatches(String name,String regexp) {
String key = "users." + name + ".location";String location = attributes.get(key);if (location == null)
return false;else
return Pattern.matches(regexp, location);}
}
5015-214
Narrowinglockscope@ThreadSafepublic class BetterAttributeStore {
@GuardedBy("this")private final Map<String, String>
attributes = new HashMap<String, String>();
public boolean userLocationMatches(String name, String regexp) {String key = "users." + name + ".location";String location;synchronized (this) {
location = attributes.get(key);}if (location == null)
return false;else
return Pattern.matches(regexp, location);}
}
5115-214
Fine-GrainedvsCoarse-GrainedTradeoffs
• Coarse-Grainedissimpler
• Fine-Grainedallowsconcurrentaccesstodifferentpartsofthestate
• Wheninvariantsspanmultiplevariants,fine-grainedlockingneedstoensurethatallrelevantpartsareusingthesamelockorarelockedtogether
• Acquiringmultiplelocksrequirescaretoavoiddeadlocks
5215-214
OvervsUndersynchronization
• Undersynchronization ->safetyhazard• Oversynchronization ->livenesshazardandreducedperformance
5315-214
GuardsandClient-SideLockingWhereistheissue?
public class ListHelper<E> {public List<E> list =
Collections.synchronizedList(new ArrayList<E>());...public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);if (absent)
list.add(x);return absent;
}}
5415-214
GuardsandClient-SideLocking
• Synchronizeontarget:public class ListHelper<E> {
public List<E> list =Collections.synchronizedList(new ArrayList<E>());
...public boolean putIfAbsent(E x) {
synchronize(list) {boolean absent = !list.contains(x);if (absent)
list.add(x);return absent;
}}
}
5515-214
Avoidingdeadlock
• Deadlockcausedbyacycleinwaits-forgraph– T1:synchronized(a){ synchronized(b){ … } }– T2:synchronized(b){ synchronized(a){ … } }
• Toavoidthesedeadlocks:– Whenthreadshavetoholdmultiplelocksatthesametime,allthreadsobtainlocksinsameorder
T1 T2b
a
5615-214
Summaryofpolicies:• Thread-confined.Athread-confinedobjectisownedexclusivelyby
andconfinedtoonethread,andcanbemodifiedbyitsowningthread.
• Sharedread-only. Asharedread-onlyobjectcanbeaccessedconcurrentlybymultiplethreadswithoutadditionalsynchronization,butcannotbemodifiedbyanythread.Sharedread-onlyobjectsincludeimmutableandeffectivelyimmutableobjects.
• Sharedthread-safe.Athread-safeobjectperformssynchronizationinternally,somultiplethreadscanfreelyaccessitthroughitspublicinterfacewithoutfurthersynchronization.
• Guarded.Aguardedobjectcanbeaccessedonlywithaspecificlockheld.Guardedobjectsincludethosethatareencapsulatedwithinotherthread-safeobjectsandpublishedobjectsthatareknowntobeguardedbyaspecificlock.
5715-214
Tradeoffs
• Strategies:– Don'tsharethestatevariableacrossthreads;–Makethestatevariableimmutable;or– Usesynchronizationwheneveraccessingthestatevariable.• Thread-safevsguarded• Coarse-grainedvsfine-grainedsynchronization
• Whentochoosewhichstrategy?– Avoidsynchronizationifpossible– Choosesimplicityoverperformancewherepossible
5815-214
Documentation
• Documentaclass'sthreadsafetyguaranteesforitsclients
• Documentitssynchronizationpolicyforitsmaintainers.
• @ThreadSafe,@GuardedBy annotationsnotstandardbutuseful
5915-214
REUSERATHERTHANBUILD:KNOWTHELIBRARIES
6015-214
java.util.concurrentisBIG(1)
• Atomicvars - java.util.concurrent.atomic– Supportvariousatomicread-modify-writeops
• Executorframework– Tasks,futures,threadpools,completionservice,etc.
• Locks- java.util.concurrent.locks– Read-writelocks,conditions,etc.
• Synchronizers– Semaphores,cyclicbarriers,countdownlatches,etc.
6115-214
java.util.concurrentisBIG(2)
• Concurrentcollections– Sharedmaps,sets,lists
• DataExchangeCollections– Blockingqueues,deques,etc.
• Pre-packagedfunctionality- java.util.arrays– Parallelsort,parallelprefix
6215-214
ParallelCollections
• Java1.2:Collections.synchronizedMap(map)• Java5:ConcurrentMap– putIfAbsent,replace,…builtin– Fine-grainedsynchronization
• BlockingQueue,CopyOnWriteArrayList,…
6315-214
Summary
• Threedesignstrategiesforachievingsafety:Threadlocality,immutabilityandsynchronization
• Tradeoffsforsynchronization– thread-safevsguarding– fine-grainedvscoarse-grained– simplicityvsperformance
• Avoidingdeadlocks• Reuseratherthanbuildabstractions;knowthelibraries
6415-214
RecommendedReadings
• Goetzetal.JavaConcurrencyInPractice.PearsonEducation,2006,Chapters2-5,11
• Lea,Douglas.ConcurrentprogramminginJava:designprinciplesandpatterns.Addison-WesleyProfessional,2000.