java並發基礎之The volatile keyword
java並發基礎之The volatile keyword
Why synchronized?
One of the biggest changes in concurrent programming in recent years has been in
the realm of hardware. It wasn’t that many years ago that a working programmer
could go for years on end without encountering a system that had more than one or
at most two processing cores. It was thus possible to think of concurrent programming as being about the timesharing of the CPU—threads swapping on and off a single core.
Today, anything larger than a mobile phone has multiple cores, so the mental
model should be different too, encompassing multiple threads all running on different cores at the same physical moment (and potentially operating on shared data).
You can see this in figure 4.4.
For efficiency, each thread that is running simultaneously
may have its own cached copy of data being operated on. With this picture in
mind, let’s turn to the question of the choice of keyword used to denote a locked section or method.
We asked earlier, what is it that’s being synchronized in the code in listing 4.1? The
answer is:
The memory representation in different threads of the object being locked is what
is being synchronized. That is, after the
synchronized block (or method) has completed, any and all changes that were made
to the object being locked
are flushed back
to main memory before the lock is released,
as illustrated in figure 4.5.
In addition, when a synchronized block
is entered, then after the lock has been acquired, any changes to the locked object are
read in
from main memory, so the thread with the lock is synchronized to main memory’s view of the object before the code in the locked section begins to execute.
The volatile keyword
Java has had the volatile keyword since the dawn of time (Java 1.0), and it’s used as a
simple way to deal with the synchronization of object fields, including primitives. The
following rules govern a volatile field:
■ The value seen by a thread is
always reread from main memory before use.
■ Any value written by a thread is
always flushed through to main memory before
the instruction completes.
This can be thought of as acting like a tiny little synchronized block around the operation. It allows the programmer to write simplified code, but at the cost of the extra
flushes on every access. Notice also that
the volatile variable doesn’t introduce any
locks, so you can’t deadlock by using volatile variables.
One slightly more subtle consequence of volatile variables is that for true threadsafety, a volatile variable should
only be used to model a variable where writes to thevariable don’t depend on the current state (the read state) of the variable. For caseswhere the current state matters, you must always introduce a lock to be completely safe.
Immutability
One technique that can be of great value is the use of
immutable objects. These are
objects that either have no state, or that
have only final fields (which must therefore
be populated in the constructors of the objects). These are always safe and live,
because their state can’t be mutated, so they can never be in an inconsistent state.
One problem is that any values that are required to initialize a particular object
must be passed into the constructor. This can lead to unwieldy constructor calls, with
many parameters. Alternatively, many coders use a FactoryMethod instead. This can
be as simple as using a static method on the class, instead of a constructor, to produce new objects. The constructors are usually made protected or private, so that
the static FactoryMethods are the only way of instantiating.
This still has the problem of potentially needing many parameters to be passed in to
the FactoryMethod. This isn’t always very convenient, especially when you may need
to accumulate state from several sources before creating a new immutable object.
To solve this, you can use the Builder pattern. This is a combination of two constructs: a static inner class that implements a generic builder interface, and a private
constructor for the immutable class itself.
The static inner class is the builder for the immutable class, and it provides the
only way that a developer can get hold of new instances of the immutable type. One
very common implementation is for the Builder class to have exactly the same fields
as the immutable class, but to allow mutation of the fields.
This listing shows how you might use this to model a microblogging update (again,
building on the earlier listings in this chapter).
public interface ObjBuilder {
T build();
}
public class Update {
private final Author author;
private final String updateText;
private Update( Builder b_ )
{
author = b_.author;
updateText = b_.updateText;
}
public static class Builder implements ObjBuilder {
private Author author;
private String updateText;
public Builder author( Author author_ )
{
author = author_;
return(this);
}
public Builder updateText( String updateText_ )
{
updateText = updateText_;
return(this);
}
public Update build()
{
return(new Update( this ) );
}
}
}
With this code, you could then create a new Update object like this:
Update.Builder ub = new Update.Builder();
Update u = ub.author(myAuthor).updateText("Hello").build();
One last point about immutable
objects—the final keyword only applies
to the object directly pointed to. As you
can see in figure 4.6, the reference to the
main object can’t be assigned to point at
object 3, but within the object, the reference to 1 can be swung to point at object 2.
Another way of saying this is that a final
reference can point at an object that has
nonfinal fields.
Immutability is a very powerful technique, and you should use it whenever feasible.
Sometimes it’s just not possible to develop efficiently with only immutable objects,
because every change to an object’s state requires a new object to be spun up. So we’re
left with the necessity of dealing with mutable objects.
讀書筆記:The Well-Grounded Java Develope