12 Java Concurrency In Practice although not nearly as often as many developers believe-but it is always a good practice first to make your code right, and then make it fast.Even then,pursue optimization only if your performance measurements and requirements tell you that you must,and if those same measurements tell you that your optimizations actually made a difference under realistic conditions. [1]In concurrent code,this practice should be adhered to even more than usual.Because concurrency bugs are so difficult to reproduce and debug,the benefit of a small performance gain on some infrequently used code path may well be dwarfed by the risk that the program will fail in the field. If you decide that you simply must break encapsulation,all is not lost.It is still possible to make your program thread- safe,it is just a lot harder.Moreover,the thread safety of your program will be more fragile,increasing not only development cost and risk but maintenance cost and risk as well.Chapter 4 characterizes the conditions under which it is safe to relax encapsulation of state variables. We've used the terms "thread-safe class"and "thread-safe program"nearly interchangeably thus far.Is a thread-safe program one that is constructed entirely of thread-safe classes?Not necessarily-a program that consists entirely of thread-safe classes may not be thread-safe,and a thread-safe program may contain classes that are not thread-safe. The issues surrounding the composition of thread-safe classes are also taken up in Chapter 4.In any case,the concept of a thread-safe class makes sense only if the class encapsulates its own state.Thread safety may be a term that is applied to code,but it is about state,and it can only be applied to the entire body of code that encapsulates its state,which may be an object or an entire program. 2.1.What is Thread Safety? Defining thread safety is surprisingly tricky.The more formal attempts are so complicated as to offer little practical guidance or intuitive understanding,and the rest are informal descriptions that can seem downright circular.A quick Google search turns up numerous"definitions"like these: ..can be called from multiple program threads without unwanted interactions between the threads. ..may be called by more than one thread at a time without requiring any other action on the caller's part. Given definitions like these,it's no wonder we find thread safety confusing!They sound suspiciously like "a class is thread-safe if it can be used safely from multiple threads."You can't really argue with such a statement,but it doesn't offer much practical help either.How do we tell a thread-safe class from an unsafe one?What do we even mean by "safe"? At the heart of any reasonable definition of thread safety is the concept of correctness.If our definition of thread safety is fuzzy,it is because we lack a clear definition of correctness. Correctness means that a class conforms to its specification.A good specification defines invariants constraining an object's state and post-conditions describing the effects of its operations.Since we often don't write adequate specifications for our classes,how can we possibly know they are correct?We can't,but that doesn't stop us from using them anyway once we've convinced ourselves that "the code works".This "code confidence"is about as close as many of us get to correctness,so let's just assume that single-threaded correctness is something that"we know it when we see it".Having optimistically defined "correctness"as something that can be recognized,we can now define thread safety in a somewhat less circular way:a class is thread-safe when it continues to behave correctly when accessed from multiple threads. A class is thread-safe if it behaves correctly when accessed from multiple threads,regardless of the scheduling or interleaving of the execution of those threads by the runtime environment,and with no additional synchronization or other coordination on the part of the calling code. Since any single-threaded program is also a valid multithreaded program,it cannot be thread-safe if it is not even correct in a single-threaded environment.R21 If an object is correctly implemented,no sequence of operations-calls to public methods and reads or writes of public fields-should be able to violate any of its invariants or post-conditions.No set of operations performed sequentially or concurrently on instances of a thread-safe class can cause an instance to be in an invalid state. [2]If the loose use of "correctness"here bothers you,you may prefer to think of a thread-safe class as one that is no more broken in a concurrent environment than in a single-threaded environment
12 Java Concurrency In Practice although not nearly as often as many developers believeͲbut it is always a good practice first to make your code right, and then make it fast. Even then, pursue optimization only if your performance measurements and requirements tell you that you must, and if those same measurements tell you that your optimizations actually made a difference under realistic conditions. [1] [1] In concurrent code, this practice should be adhered to even more than usual. Because concurrency bugs are so difficult to reproduce and debug, the benefit of a small performance gain on some infrequently used code path may well be dwarfed by the risk that the program will fail in the field. If you decide that you simply must break encapsulation, all is not lost. It is still possible to make your program threadͲ safe, it is just a lot harder. Moreover, the thread safety of your program will be more fragile, increasing not only development cost and risk but maintenance cost and risk as well. Chapter 4 characterizes the conditions under which it is safe to relax encapsulation of state variables. We've used the terms "threadͲsafe class" and "threadͲsafe program" nearly interchangeably thus far. Is a threadͲsafe program one that is constructed entirely of threadͲsafe classes? Not necessarily Ͳa program that consists entirely of threadͲsafe classes may not be threadͲsafe, and a threadͲsafe program may contain classes that are not threadͲsafe. The issues surrounding the composition of threadͲsafe classes are also taken up in Chapter 4. In any case, the concept of a threadͲsafe class makes sense only if the class encapsulates its own state. Thread safety may be a term that is applied to code, but it is about state, and it can only be applied to the entire body of code that encapsulates its state, which may be an object or an entire program. 2.1. What is Thread Safety? Defining thread safety is surprisingly tricky. The more formal attempts are so complicated as to offer little practical guidance or intuitive understanding, and the rest are informal descriptions that can seem downright circular. A quick Google search turns up numerous "definitions" like these: . . . can be called from multiple program threads without unwanted interactions between the threads. . . .may be called by more than one thread at a time without requiring any other action on the caller's part. Given definitions like these, it's no wonder we find thread safety confusing! They sound suspiciously like "a class is threadͲsafe if it can be used safely from multiple threads." You can't really argue with such a statement, but it doesn't offer much practical help either. How do we tell a threadͲsafe class from an unsafe one? What do we even mean by "safe"? At the heart of any reasonable definition of thread safety is the concept of correctness. If our definition of thread safety is fuzzy, it is because we lack a clear definition of correctness. Correctness means that a class conforms to its specification. A good specification defines invariants constraining an object's state and post Ͳ conditions describing the effects of its operations. Since we often don't write adequate specifications for our classes, how can we possibly know they are correct? We can't, but that doesn't stop us from using them anyway once we've convinced ourselves that "the code works". This "code confidence" is about as close as many of us get to correctness, so let's just assume that singleͲthreaded correctness is something that "we know it when we see it". Having optimistically defined "correctness" as something that can be recognized, we can now define thread safety in a somewhat less circular way: a class is threadͲsafe when it continues to behave correctly when accessed from multiple threads. A class is threadͲsafe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code. Since any singleͲthreaded program is also a valid multithreaded program, it cannot be threadͲsafe if it is not even correct in a singleͲthreaded environment. [2] If an object is correctly implemented, no sequence of operationsͲcalls to public methods and reads or writes of public fieldsͲshould be able to violate any of its invariants or postͲconditions. No set of operations performed sequentially or concurrently on instances of a threadͲsafe class can cause an instance to be in an invalid state. [2] If the loose use of "correctness" here bothers you, you may prefer to think of a threadͲsafe class as one that is no more broken in a concurrent environment than in a singleͲthreaded environment.
5288B5287B5286B5249B5230B5229B5200B5148B4916B4722B4721B4720B4719B4594B4593B4569B4568B4567B45 66B4565B4564B4427B4426B4410B4409B4345B4218B4217B4216B4215B3636B3635B3469B3468B3465B3455B3454 13 B3453B3449B3221B3220B3219B3214B3059B3058B3057B2535B2534B2190B2189B2188B2-14 BChapter2.Thread Safety Thread-safe classes encapsulate any needed synchronization so that clients need not provide their own 2.1.1.Example:A Stateless Servlet In Chapter 1,we listed a number of frameworks that create threads and call your components from those threads, leaving you with the responsibility of making your components thread-safe.Very often,thread-safety requirements stem not from a decision to use threads directly but from a decision to use a facility like the Servlets framework.We're going to develop a simple example-a servlet-based factorization service-and slowly extend it to add features while preserving its thread safety. Listing 2.1 shows our simple factorization servlet.It unpacks the number to be factored from the servlet request,factors it,and packages the results into the servlet response. Listing 2.1.A Stateless Servlet. @Threadsafe public class statelessFactorizer implements servlet public void service(servletRequest req,ServletResponse resp){ BigInteger i extractFromRequest(req); BigInteger[]factors factor(i): encodeIntoResponse(resp,factors); StatelessFactorizer is,like most servlets,stateless:it has no fields and references no fields from other classes.The transient state for a particular computation exists solely in local variables that are stored on the thread's stack and are accessible only to the executing thread.One thread accessing a statelessFactorizer cannot influence the result of another thread accessing the same statelessFactorizer;because the two threads do not share state,it is as if they were accessing different instances.Since the actions of a thread accessing a stateless object cannot affect the correctness of operations in other threads,stateless objects are thread-safe Stateless objects are always thread-safe. The fact that most servlets can be implemented with no state greatly reduces the burden of making servlets thread- safe.It is only when servlets want to remember things from one request to another that the thread safety requirement becomes an issue. 2.2.Atomicity What happens when we add one element of state to what was a stateless object?Suppose we want to add a "hit counter"that measures the number of requests processed.The obvious approach is to add a long field to the servlet and increment it on each request,as shown in UnsafecountingFactorizer in Listing 2.2. Unfortunately,UnsafecountingFactorizer is not thread-safe,even though it would work just fine in a single-threaded environment.Just like unsafesequence on page 6,it is susceptible to lost updates.While the increment operation, ++count,may look like a single action because of its compact syntax,it is not atomic,which means that it does not execute as a single,indivisible operation.Instead,it is shorthand for a sequence of three discrete operations:fetch the current value,add one to it,and write the new value back.This is an example of a read-modify-write operation,in which the resulting state is derived from the previous state
13 5288B5287B5286B5249B5230B5229B5200B5148B4916B4722B4721B4720B4719B4594B4593B4569B4568B4567B45 66B4565B4564B4427B4426B4410B4409B4345B4218B4217B4216B4215B3636B3635B3469B3468B3465B3455B3454 B3453B3449B3221B3220B3219B3214B3059B3058B3057B2535B2534B2190B2189B2188B2Ͳ14BChapter 2. Thread Safety ThreadͲsafe classes encapsulate any needed synchronization so that clients need not provide their own. 2.1.1. Example: A Stateless Servlet In Chapter 1, we listed a number of frameworks that create threads and call your components from those threads, leaving you with the responsibility of making your components threadͲsafe. Very often, threadͲsafety requirements stem not from a decision to use threads directly but from a decision to use a facility like the Servlets framework. We're going to develop a simple example Ͳa servletͲbased factorization service Ͳand slowly extend it to add features while preserving its thread safety. Listing 2.1 shows our simple factorization servlet. It unpacks the number to be factored from the servlet request, factors it, and packages the results into the servlet response. Listing 2.1. A Stateless Servlet. @ThreadSafe public class StatelessFactorizer implements Servlet { public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); encodeIntoResponse(resp, factors); } } StatelessFactorizer is, like most servlets, stateless: it has no fields and references no fields from other classes. The transient state for a particular computation exists solely in local variables that are stored on the thread's stack and are accessible only to the executing thread. One thread accessing a StatelessFactorizer cannot influence the result of another thread accessing the same StatelessFactorizer; because the two threads do not share state, it is as if they were accessing different instances. Since the actions of a thread accessing a stateless object cannot affect the correctness of operations in other threads, stateless objects are threadͲsafe. Stateless objects are always threadͲsafe. The fact that most servlets can be implemented with no state greatly reduces the burden of making servlets threadͲ safe. It is only when servlets want to remember things from one request to another that the thread safety requirement becomes an issue. 2.2. Atomicity What happens when we add one element of state to what was a stateless object? Suppose we want to add a "hit counter" that measures the number of requests processed. The obvious approach is to add a long field to the servlet and increment it on each request, as shown in UnsafeCountingFactorizer in Listing 2.2. Unfortunately, UnsafeCountingFactorizer is not threadͲsafe, even though it would work just fine in a singleͲthreaded environment. Just like UnsafeSequence on page 6, it is susceptible to lost updates. While the increment operation, ++count, may look like a single action because of its compact syntax, it is not atomic, which means that it does not execute as a single, indivisible operation. Instead, it is shorthand for a sequence of three discrete operations: fetch the current value, add one to it, and write the new value back. This is an example of a readͲmodifyͲwrite operation, in which the resulting state is derived from the previous state.
Java Concurrency In Practice Listing 2.2.Servlet that Counts Requests without the Necessary Synchronization.Don't Do this. @NotThreadsafe public class UnsafeCountingFactorizer 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(1); ++count; encodeIntoResponse(resp,factors); Figure 1.1 on page 6 shows what can happen if two threads try to increment a counter simultaneously without synchronization.If the counter is initially 9,with some unlucky timing each thread could read the value,see that it is 9, add one to it,and each set the counter to 10.This is clearly not what is supposed to happen;an increment got lost along the way,and the hit counter is now permanently off by one. You might think that having a slightly inaccurate count of hits in a web-based service is an acceptable loss of accuracy, and sometimes it is.But if the counter is being used to generate sequences or unique object identifiers,returning the same value from multiple invocations could cause serious data integrity problems.3 The possibility of incorrect results in the presence of unlucky timing is so important in concurrent programming that it has a name:a race condition. [3]The approach taken by UnsafeSequence and UnsafeCountingFactorizer has other serious problems,including the possibility of stale data (Section 3.1.1). 2.2.1.Race Conditions UnsafecountingFactorizer has several race conditions that make its results unreliable.A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime;in other words,when getting the right answer relies on lucky timing.4 The most common type of race condition is check- then-act,where a potentially stale observation is used to make a decision on what to do next. [4]The term race condition is often confused with the related term data race,which arises when synchronization is not used to coordinate all access to a shared non-final field.You risk a data race whenever a thread writes a variable that might next be read by another thread or reads a variable that might have last been written by another thread if both threads do not use synchronization;code with data races has no useful defined semantics under the Java Memory Model.Not all race conditions are data races,and not all data races are race conditions,but they both can cause concurrent programs to fail in unpredictable ways.UnsafecountingFactorizer has both race conditions and data races.See Chapter 16 for more on data races. We often encounter race conditions in real life.Let's say you planned to meet a friend at noon at the Starbucks on University Avenue.But when you get there,you realize there are two Starbucks on University Avenue,and you're not sure which one you agreed to meet at.At 12:10,you don't see your friend at Starbucks A,so you walk over to Starbucks B to see if he's there,but he isn't there either.There are a few possibilities:your friend is late and not at either Starbucks;your friend arrived at Starbucks A after you left;or your friend was at Starbucks B,but went to look for you, and is now en route to Starbucks A.Let's assume the worst and say it was the last possibility.Now it's 12:15,you've both been to both Starbucks,and you're both wondering if you've been stood up.What do you do now?Go back to the other Starbucks?How many times are you going to go back and forth?Unless you have agreed on a protocol,you could both spend the day walking up and down University Avenue,frustrated and undercaffeinated. The problem with the"I'll just nip up the street and see if he's at the other one"approach is that while you're walking up the street,your friend might have moved.You look around Starbucks A,observe "he's not here",and go looking for him. And you can do the same for Starbucks B,but not at the same time.It takes a few minutes to walk up the street,and during those few minutes,the state of the system may have changed. The Starbucks example illustrates a race condition because reaching the desired outcome(meeting your friend)depends on the relative timing of events(when each of you arrives at one Starbucks or the other,how long you wait there before switching,etc).The observation that he is not at Starbucks A becomes potentially invalid as soon as you walk out the front door;he could have come in through the back door and you wouldn't know.It is this invalidation of observations that characterizes most race conditions using a potentially stale observation to make a decision or perform a computation.This type of race condition is called check-then-act:you observe something to be true(file X doesn't exist)
14 Java Concurrency In Practice Listing 2.2. Servlet that Counts Requests without the Necessary Synchronization. Don't Do this. @NotThreadSafe public class UnsafeCountingFactorizer 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); } } Figure 1.1 on page 6 shows what can happen if two threads try to increment a counter simultaneously without synchronization. If the counter is initially 9, with some unlucky timing each thread could read the value, see that it is 9, add one to it, and each set the counter to 10. This is clearly not what is supposed to happen; an increment got lost along the way, and the hit counter is now permanently off by one. You might think that having a slightly inaccurate count of hits in a webͲbased service is an acceptable loss of accuracy, and sometimes it is. But if the counter is being used to generate sequences or unique object identifiers, returning the same value from multiple invocations could cause serious data integrity problems.[3] The possibility of incorrect results in the presence of unlucky timing is so important in concurrent programming that it has a name: a race condition. [3] The approach taken by UnsafeSequence and UnsafeCountingFactorizer has other serious problems, including the possibility of stale data (Section 3.1.1). 2.2.1. Race Conditions UnsafeCountingFactorizer has several race conditions that make its results unreliable. A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime; in other words, when getting the right answer relies on lucky timing.[4] The most common type of race condition is checkͲ thenͲact, where a potentially stale observation is used to make a decision on what to do next. [4] The term race condition is often confused with the related term data race, which arises when synchronization is not used to coordinate all access to a shared nonͲfinal field. You risk a data race whenever a thread writes a variable that might next be read by another thread or reads a variable that might have last been written by another thread if both threads do not use synchronization; code with data races has no useful defined semantics under the Java Memory Model. Not all race conditions are data races, and not all data races are race conditions, but they both can cause concurrent programs to fail in unpredictable ways. UnsafeCountingFactorizer has both race conditions and data races. See Chapter 16 for more on data races. We often encounter race conditions in real life. Let's say you planned to meet a friend at noon at the Starbucks on University Avenue. But when you get there, you realize there are two Starbucks on University Avenue, and you're not sure which one you agreed to meet at. At 12:10, you don't see your friend at Starbucks A, so you walk over to Starbucks B to see if he's there, but he isn't there either. There are a few possibilities: your friend is late and not at either Starbucks; your friend arrived at Starbucks A after you left; or your friend was at Starbucks B, but went to look for you, and is now en route to Starbucks A. Let's assume the worst and say it was the last possibility. Now it's 12:15, you've both been to both Starbucks, and you're both wondering if you've been stood up. What do you do now? Go back to the other Starbucks? How many times are you going to go back and forth? Unless you have agreed on a protocol, you could both spend the day walking up and down University Avenue, frustrated and undercaffeinated. The problem with the "I'll just nip up the street and see if he's at the other one" approach is that while you're walking up the street, your friend might have moved. You look around Starbucks A, observe "he's not here", and go looking for him. And you can do the same for Starbucks B, but not at the same time. It takes a few minutes to walk up the street, and during those few minutes, the state of the system may have changed. The Starbucks example illustrates a race condition because reaching the desired outcome (meeting your friend) depends on the relative timing of events (when each of you arrives at one Starbucks or the other, how long you wait there before switching, etc). The observation that he is not at Starbucks A becomes potentially invalid as soon as you walk out the front door; he could have come in through the back door and you wouldn't know. It is this invalidation of observations that characterizes most race conditions Ͳ using a potentially stale observation to make a decision or perform a computation. This type of race condition is called checkͲthenͲact: you observe something to be true (file X doesn't exist)
5288B5287B5286B5249B5230B5229B5200B5148B4916B4722B4721B4720B4719B4594B4593B4569B4568B4567B45 66B4565B4564B4427B4426B4410B4409B4345B4218B4217B4216B4215B3636B3635B3469B3468B3465B3455B3454 15 B3453B3449B3221B3220B3219B3214B3059B3058B3057B2535B2534B2190B2189B2188B2-14 BChapter2.Thread Safety and then take action based on that observation (create X);but in fact the observation could have become invalid between the time you observed it and the time you acted on it(someone else created X in the meantime),causing a problem(unexpected exception,overwritten data,file corruption). 2.2.2.Example:Race Conditions in Lazy Initialization A common idiom that uses check-then-act is lazy initialization.The goal of lazy initialization is to defer initializing an object until it is actually needed while at the same time ensuring that it is initialized only once.LazyInitRace in Listing 2.3 illustrates the lazy initialization idiom.The getInstance method first checks whether the Expensiveobject has already been initialized,in which case it returns the existing instance;otherwise it creates a new instance and returns it after retaining a reference to it so that future invocations can avoid the more expensive code path. Listing 2.3.Race Condition in Lazy Initialization.Don't Do this. @NotThreadsafe public class LazyInitRace private Expensiveobject instance null; public Expensiveobject_getInstance(){ if (instance ==null) instance new Expensiveobject(); return instance; LazyInitRace has race conditions that can undermine its correctness.Say that threads A and B execute getInstance at the same time.A sees that instance is null,and instantiates a new Expensiveobject.B also checks if instance is nu11.Whether instance is null at this point depends unpredictably on timing,including the vagaries of scheduling and how long A takes to instantiate the Expensiveobject and set the instance field.If instance is null when B examines it,the two callers to getInstance may receive two different results,even though getInstance is always supposed to return the same instance. The hit-counting operation in UnsafecountingFactorizer has another sort of race condition.Read-modify-write operations,like incrementing a counter,define a transformation of an object's state in terms of its previous state.To increment a counter,you have to know its previous value and make sure no one else changes or uses that value while you are in mid-update. Like most concurrency errors,race conditions don't always result in failure:some unlucky timing is also required.But race conditions can cause serious problems.If LazyInitRace is used to instantiate an application-wide registry,having it return different instances from multiple invocations could cause registrations to be lost or multiple activities to have inconsistent views of the set of registered objects.If Unsafesequence is used to generate entity identifiers in a persistence framework,two distinct objects could end up with the same ID,violating identity integrity constraints. 2.2.3.Compound Actions Both LazyInitRace and UnsafeCountingFactorizer contained a sequence of operations that needed to be atomic,or indivisible,relative to other operations on the same state.To avoid race conditions,there must be a way to prevent other threads from using a variable while we're in the middle of modifying it,so we can ensure that other threads can observe or modify the state only before we start or after we finish,but not in the middle. Operations A and B are atomic with respect to each other if,from the perspective of a thread executing A,when another thread executes B,either all of B has executed or none of it has.An atomic operation is one that is atomic with respect to all operations,including itself,that operate on the same state. If the increment operation in unsafesequence were atomic,the race condition illustrated in Figure 1.1 on page 6 could not occur,and each execution of the increment operation would have the desired effect of incrementing the counter by exactly one.To ensure thread safety,check-then-act operations (like lazy initialization)and read-modify-write operations(like increment)must always be atomic.We refer collectively to check-then-act and read-modify-write sequences as compound actions:sequences of operations that must be executed atomically in order to remain thread-
15 5288B5287B5286B5249B5230B5229B5200B5148B4916B4722B4721B4720B4719B4594B4593B4569B4568B4567B45 66B4565B4564B4427B4426B4410B4409B4345B4218B4217B4216B4215B3636B3635B3469B3468B3465B3455B3454 B3453B3449B3221B3220B3219B3214B3059B3058B3057B2535B2534B2190B2189B2188B2Ͳ14BChapter 2. Thread Safety and then take action based on that observation (create X); but in fact the observation could have become invalid between the time you observed it and the time you acted on it (someone else created X in the meantime), causing a problem (unexpected exception, overwritten data, file corruption). 2.2.2. Example: Race Conditions in Lazy Initialization A common idiom that uses checkͲthenͲact is lazy initialization. The goal of lazy initialization is to defer initializing an object until it is actually needed while at the same time ensuring that it is initialized only once. LazyInitRace in Listing 2.3 illustrates the lazy initialization idiom. The getInstance method first checks whether the ExpensiveObject has already been initialized, in which case it returns the existing instance; otherwise it creates a new instance and returns it after retaining a reference to it so that future invocations can avoid the more expensive code path. Listing 2.3. Race Condition in Lazy Initialization. Don't Do this. @NotThreadSafe public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) instance = new ExpensiveObject(); return instance; } } LazyInitRace has race conditions that can undermine its correctness. Say that threads A and B execute getInstance at the same time. A sees that instance is null, and instantiates a new ExpensiveObject. B also checks if instance is null. Whether instance is null at this point depends unpredictably on timing, including the vagaries of scheduling and how long A takes to instantiate the ExpensiveObject and set the instance field. If instance is null when B examines it, the two callers to getInstance may receive two different results, even though getInstance is always supposed to return the same instance. The hitͲcounting operation in UnsafeCountingFactorizer has another sort of race condition. ReadͲmodifyͲwrite operations, like incrementing a counter, define a transformation of an object's state in terms of its previous state. To increment a counter, you have to know its previous value and make sure no one else changes or uses that value while you are in midͲupdate. Like most concurrency errors, race conditions don't always result in failure: some unlucky timing is also required. But race conditions can cause serious problems. If LazyInitRace is used to instantiate an applicationͲwide registry, having it return different instances from multiple invocations could cause registrations to be lost or multiple activities to have inconsistent views of the set of registered objects. If UnsafeSequence is used to generate entity identifiers in a persistence framework, two distinct objects could end up with the same ID, violating identity integrity constraints. 2.2.3. Compound Actions Both LazyInitRace and UnsafeCountingFactorizer contained a sequence of operations that needed to be atomic, or indivisible, relative to other operations on the same state. To avoid race conditions, there must be a way to prevent other threads from using a variable while we're in the middle of modifying it, so we can ensure that other threads can observe or modify the state only before we start or after we finish, but not in the middle. Operations A and B are atomic with respect to each other if, from the perspective of a thread executing A, when another thread executes B, either all of B has executed or none of it has. An atomic operation is one that is atomic with respect to all operations, including itself, that operate on the same state. If the increment operation in UnsafeSequence were atomic, the race condition illustrated in Figure 1.1 on page 6 could not occur, and each execution of the increment operation would have the desired effect of incrementing the counter by exactly one. To ensure thread safety, checkͲthenͲact operations (like lazy initialization) and readͲmodifyͲwrite operations (like increment) must always be atomic. We refer collectively to checkͲthenͲact and readͲmodifyͲwrite sequences as compound actions: sequences of operations that must be executed atomically in order to remain threadͲ
16 Java Concurrency In Practice safe.In the next section,we'll consider locking,Java's built-in mechanism for ensuring atomicity.For now,we're going to fix the problem another way,by using an existing thread-safe class,as shown in countingFactorizer in Listing 2.4. Listing 2.4.Servlet that Counts Requests Using AtomicLong. @Threadsafe public 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); The java.util.concurrent.atomic package contains atomic variable classes for effecting atomic state transitions on numbers and object references.By replacing the long counter with an AtomicLong,we ensure that all actions that access the counter state are atomic.I5)Because the state of the servlet is the state of the counter and the counter is thread-safe,our servlet is once again thread-safe. [5]CountingFactorizer calls incrementAndGet to increment the counter,which also returns the incremented value;in this case the return value is ignored. We were able to add a counter to our factoring servlet and maintain thread safety by using an existing thread-safe class to manage the counter state,AtomicLong.When a single element of state is added to a stateless class,the resulting class will be thread-safe if the state is entirely managed by a thread-safe object.But,as we'll see in the next section, going from one state variable to more than one is not necessarily as simple as going from zero to one. Where practical,use existing thread-safe objects,like Atomi cLong,to manage your class's state.It is simpler to reason about the possible states and state transitions for existing thread-safe objects than it is for arbitrary state variables,and this makes it easier to maintain and verify thread safety. 2.3.Locking We were able to add one state variable to our servlet while maintaining thread safety by using a thread-safe object to manage the entire state of the servlet.But if we want to add more state to our servlet,can we just add more thread- safe state variables? Imagine that we want to improve the performance of our servlet by caching the most recently computed result,just in case two consecutive clients request factorization of the same number.(This is unlikely to be an effective caching strategy;we offer a better one in Section 5.6.)To implement this strategy,we need to remember two things:the last number factored,and its factors. We used AtomicLong to manage the counter state in a thread-safe manner;could we perhaps use its cousin, AtomicReference,[6]to manage the last number and its factors?An attempt at this is shown in UnsafecachingFactorizer in Listing 2.5. [6]Just as AtomicLong is a thread-safe holder class for a long integer,AtomicReference is a thread safe holder class for an object reference.Atomic variables and their benefits are covered in Chapter 15
16 Java Concurrency In Practice safe. In the next section, we'll consider locking, Java's builtͲin mechanism for ensuring atomicity. For now, we're going to fix the problem another way, by using an existing threadͲsafe class, as shown in CountingFactorizer in Listing 2.4. Listing 2.4. Servlet that Counts Requests Using AtomicLong. @ThreadSafe public 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); } } The java.util.concurrent.atomic package contains atomic variable classes for effecting atomic state transitions on numbers and object references. By replacing the long counter with an AtomicLong, we ensure that all actions that access the counter state are atomic. [5] Because the state of the servlet is the state of the counter and the counter is threadͲsafe, our servlet is once again threadͲsafe. [5] CountingFactorizer calls incrementAndGet to increment the counter, which also returns the incremented value; in this case the return value is ignored. We were able to add a counter to our factoring servlet and maintain thread safety by using an existing threadͲsafe class to manage the counter state, AtomicLong. When a single element of state is added to a stateless class, the resulting class will be threadͲsafe if the state is entirely managed by a threadͲsafe object. But, as we'll see in the next section, going from one state variable to more than one is not necessarily as simple as going from zero to one. Where practical, use existing threadͲsafe objects, like AtomicLong, to manage your class's state. It is simpler to reason about the possible states and state transitions for existing threadͲsafe objects than it is for arbitrary state variables, and this makes it easier to maintain and verify thread safety. 2.3. Locking We were able to add one state variable to our servlet while maintaining thread safety by using a threadͲsafe object to manage the entire state of the servlet. But if we want to add more state to our servlet, can we just add more threadͲ safe state variables? Imagine that we want to improve the performance of our servlet by caching the most recently computed result, just in case two consecutive clients request factorization of the same number. (This is unlikely to be an effective caching strategy; we offer a better one in Section 5.6.) To implement this strategy, we need to remember two things: the last number factored, and its factors. We used AtomicLong to manage the counter state in a threadͲsafe manner; could we perhaps use its cousin, AtomicReference, [6] to manage the last number and its factors? An attempt at this is shown in UnsafeCachingFactorizer in Listing 2.5. [6] Just as AtomicLong is a threadͲsafe holder class for a long integer, AtomicReference is a thread safe holder class for an object reference. Atomic variables and their benefits are covered in Chapter 15