Download Best Practices for Thread-Safety & Encapsulation in Java: 2007 JavaOne Conference and more Exams Programming Languages in PDF only on Docsity! 2007 JavaOneSM Conference | Session TS-2388 | TS-2388 Effective Concurrency for the Java™ Platform Brian Goetz Senior Staff Engineer Sun Microsystems, Inc. brian.goetz@sun.com 2007 JavaOneSM Conference | Session TS-2388 | 2 The Big Picture Writing correct concurrent code is difficult, but not impossible Using good object-oriented design techniques can make it easier 2007 JavaOneSM Conference | Session TS-2388 | 5 Agenda Introduction Rules for Writing Thread-Safe Code Document Thread-Safety Intent and Implementation Encapsulate Data and Synchronization Prefer Immutable Objects Exploit Effective Immutability Rules for Structuring Concurrent Applications Think Tasks, Not Threads Build Resource-Management Into Your Architecture Decouple Identification of Work from Execution Rules for Improving Scalability Find and Eliminate the Serialization 2007 JavaOneSM Conference | Session TS-2388 | 6 Introduction ● This talk is about identifying patterns for concurrent code that are less fragile ● Conveniently, many are the good practices we already know ● Though sometimes we forget the basics ● Feel free to break (almost) all the rules here ● But be prepared to pay for it at maintenance time ● Remember the core language value: Reading code is more important than writing code 2007 JavaOneSM Conference | Session TS-2388 | 7 Agenda Introduction Rules for Writing Thread-Safe Code Document Thread-Safety Intent and Implementation Encapsulate Data and Synchronization Prefer Immutable Objects Exploit Effective Immutability Rules for Structuring Concurrent Applications Think Tasks, Not Threads Build Resource-Management Into Your Architecture Decouple Identification of Work from Execution Rules for Improving Scalability Find and Eliminate the Serialization 2007 JavaOneSM Conference | Session TS-2388 | 10 Document Thread-Safety ● Use @GuardedBy to document your locking protocols ● Annotating a field with @GuardedBy("this") means: ● Only access the field when holding the lock on “this” @ThreadSafe public class PositiveInteger { // INVARIANT: value > 0 @GuardedBy("this") private int value = 1; public synchronized int getValue() { return value; } public void setValue(int value) { if (value <= 0) throw new IllegalArgumentException(....); synchronized (this) { this.value = value; } } } ● Simplifies maintenance and avoids common mistakes ● Like adding a new code path and forgetting to synchronize ● Improper maintenance is a big source of concurrency bugs 2007 JavaOneSM Conference | Session TS-2388 | 11 Document Thread-Safety ● For primitive variables, @GuardedBy is straightforward ● But what about @GuardedBy("this") Set<Rock> knownRocks = new HashSet<Rock>(); ● There are three different types of potentially mutable state ●The knownRocks reference ●The internal data structures in the HashSet ●The elements of the collection ● Which types of state are we talking about? All of them? ● It varies, but we can often tell from context ●Are the elements owned by the class, or by clients? ●Are the elements thread-safe? ●Is the reference to the collection mutable? @GuardedBy("this") final Set<Rock> knownRocks = .... 2007 JavaOneSM Conference | Session TS-2388 | 12 knownRocks HashSet<Rock> Rock Rock Rock Rock ● For complicated data structures, draw a diagram identifying ownership and synchronization policies ● Color each state domain with its synchronization policy @ThreadSafe public class Rock { .... } @GuardedBy("this") final Set<Rock> knownRocks = new HashSet<Rock>(); ● Very effective for designing and reviewing code! ● Frequently identifies gaps or inconsistencies in synchronization policies Document Thread-Safety 2007 JavaOneSM Conference | Session TS-2388 | 15 Encapsulate Data and Synchronization ● Encapsulation promotes clear, maintainable code ● Reduces scope of effect of code changes ● Encapsulation similarly promotes thread safety ● Reduces how much code can access a variable ● And therefore how much be examined to ensure that synchronization protocols are followed ● Thread safety is about coordinating access to shared mutable data ● Shared—might be accessed by more than one thread ● Mutable—might be modified by some thread ● Less code that accesses a variable means fewer opportunities for error 2007 JavaOneSM Conference | Session TS-2388 | 16 Encapsulate Data and Synchronization ● Encapsulation makes it sensible to talk about individual classes being thread-safe ● A body of code is thread-safe if: ● It is correct in a single-threaded environment, and ● It continues to be correct when called from multiple threads ● Regardless of interleaving of execution by the runtime ● Without additional coordination by callers ● Correct means conforms to its specification ● Often framed in terms of invariants and postconditions ● These are statements about state ● Can’t say a body of code guarantees an invariant unless no other code can modify the underlying state ● Thread-safety can only describe a body of code that manages all access to its mutable state ● Without encapsulation, that's the whole program 2007 JavaOneSM Conference | Session TS-2388 | 17 Encapsulate Data and Synchronization ● Is this code correct? Is it thread-safe? public class PositiveInteger { // INVARIANT: value > 0 @GuardedBy("this") public int value = 1; public synchronized int getValue() { return value; } public synchronized void setValue(int value) { if (value <= 0) throw new IllegalArgumentException(....); this.value = value; } } ● We can’t say unless we examine all the code that accesses value ●Doesn’t even enforce invariants in single-threaded case ●Difficult to reason about invariants when data can change at any time ●Can’t ensure data is accessed with proper synchronization 2007 JavaOneSM Conference | Session TS-2388 | 20 Encapsulate Data and Synchronization ● If a class imposes invariants on its state, it must also provide its own synchronization to protect these invariants ● Even if component classes are thread-safe! ● UserManager follows The Rule ● But still might not be thread-safe! public class UserManager { // Each known user is in exactly one of {active, inactive} private final Set<User> active = Collections.synchronizedSet(new HashSet<User>()); private final Set<User> inactive = Collections.synchronizedSet(new HashSet<User>()); // Constructor populates inactive set with known users public void activate(User u) { if (inactive.remove(u)) active.add(u); } public boolean isKnownUser(User u) { return active.contains(u) || inactive.contains(u); } } 2007 JavaOneSM Conference | Session TS-2388 | 21 Encapsulate Data and Synchronization ● In UserManager, all data is accessed with synchronization ● But still possible to see a user as neither active nor inactive ● Therefore not thread-safe—can violate its specification! ● Need to make compound operations atomic with respect to one other ● Solution: synchronize UserManager methods public class UserManager { // Each known user is in exactly one of {active, inactive} private final Set<User> active = Collections.synchronizedSet(...); private final Set<User> inactive = Collections.synchronizedSet(...); public synchronized void activate(User u) { if (inactive.remove(u)) active.add(u); } public synchronized boolean isKnownUser(User u) { return active.contains(u) || inactive.contains(u); } public Set<User> getActiveUsers() { return Collections.unmodifiableSet(active); } } 2007 JavaOneSM Conference | Session TS-2388 | 22 Encapsulate Data and Synchronization ● The problem was that synchronization was specified at a different level than the invariants ● Result: atomicity failures (race conditions) ● Could fix with client-side locking, but is fragile ● Instead, encapsulate enforcement of invariants ● All variables in an invariant should be guarded by same lock ● Hold lock for duration of operation on related variables ● Always provide synchronization at the same level as the invariants ● When composing operations on thread-safe objects, you may end up with multiple layers of synchronization ● And that’s OK! 2007 JavaOneSM Conference | Session TS-2388 | 25 Prefer Immutable Objects ● An immutable object is one whose ● State cannot be changed after construction ● All fields are final ● Not optional—critical for thread-safety of immutable objects ● Immutable objects are automatically thread-safe! ● Simpler ● Can only ever be in one state, controlled by the constructor ● Safer ● Can be freely shared with unknown or malicious code, who cannot subvert their invariants ● More scalable ● No synchronization required when sharing! ● (See Effective Java technology Item #13 for more) 2007 JavaOneSM Conference | Session TS-2388 | 26 Prefer Immutable Objects ● Most concurrency hazards stem from the need to coordinate access to mutable state ● Race conditions and data races come from insufficient synchronization ● Many other problems (e.g., deadlock) are consequences of strategies for proper coordination ● No mutable state → no need for coordination ● No race conditions, data races, deadlocks, scalability bottlenecks ● Identify immutable objects with @Immutable ● @Immutable implies @ThreadSafe ● Don’t worry about the cost of object creation ● Object lifecycle is generally cheap ● Immutable objects have some performance benefits too 2007 JavaOneSM Conference | Session TS-2388 | 27 Prefer Immutable Objects ● Even if immutability is not an option, less mutable state can still mean less coordination ● Benefits of immutability apply to individual variables as well as objects ● Final fields have special visibility guarantees ● Final fields are simpler than mutable fields ● Final is the new private ● Declare fields final wherever practical ● Worth doing extra work to avoid making fields nonfinal ● In synchronization policy diagrams, final variables provide a synchronization policy for references ● But not the referred-to object ● If you can’t get away with full immutability, seek to limit mutable state as much as possible 2007 JavaOneSM Conference | Session TS-2388 | 30 Find the Serialization ● Processor speeds flattened out around 2003 ● Moore's law now gives us more cores, not faster ones ● Increasing throughput means keeping more cores busy ● Can no longer just buy a faster box to get a speedup ● Must write programs that take advantage of additional CPUs ● Just adding more cores may not improve throughput ● Tasks must be amenable to parallelization Source: (Graphic © 2006 Herb Sutter) 2007 JavaOneSM Conference | Session TS-2388 | 31 Find the Serialization ● System throughput is governed by Amdahl’s Law ● Divides work into serial and parallel portions ● Serial work cannot be sped up by adding resources ● Parallelizable work can be ● Most tasks have a mix of serial and parallel work ● Harvesting crops can be sped up with more workers ● But additional workers will not make them grow any faster ● Amdahl’s Law says: ● F is the fraction that must be executed serially ● N is the number of available workers ● As N → infinity, speedup → 1/F ● With 50% serialization, can only speed up by a factor of two ● No matter how many processors Speedup 1 F 1−F N 2007 JavaOneSM Conference | Session TS-2388 | 32 Find the Serialization ● Every task has some sources of serialization ● You just have to know where to look ● The primary source of serialization is the exclusive lock ● The longer locks are held for, the worse it gets ● Even when tasks consist only of thread-local computation, there is still serialization inherent in task dispatching while (!shutdownRequested) { Task t = taskQueue.take(); // potential serialization Result r = t.doTask(); resultSet.add(result); // potential serialization } ● Accessing the task queue and the results container invariably involves serialization 2007 JavaOneSM Conference | Session TS-2388 | 35 For More Information ● Other sessions ● TS-2220: Testing Concurrent Software ● TS-2007: Improving Software Quality with Static Analysis ● BOF-2864: Debugging Data Races ● Books ● Java Concurrency in Practice (Goetz, et al) ● See http://www.jcip.net ● Concurrent Programming in Java (Lea) ● Effective Java (Bloch) 2007 JavaOneSM Conference | Session TS-2388 | 36 Q&A Effective Concurrency for the Java Platform Brian Goetz, Sun Microsystems 2007 JavaOneSM Conference | Session TS-2388 | TS-2388 Effective Concurrency for the Java™ Platform Brian Goetz Senior Staff Engineer Sun Microsystems, Inc. brian.goetz@sun.com