• No results found

Singletons must be thread-safe

More use cases in scoping

6.1 Defining a custom scope

6.2.1 Singletons must be thread-safe

Singletons absolutely must be thread-safe —no exceptions.

Any serious application has several threads of execu- tion that are continually operating. They may be ser- vicing different users, timers, remote clients, or any of a number of other purposes. A singleton-scoped key directly implies that only one shared instance of an object exists in the entire injector (and consequently the application). Multiple threads often end up accessing singleton instances and may even do so con- currently. Chapter 9 discusses the intricacies of thread- ing and objects in detail (with a particular emphasis on Java’s thread and memory model).

There are two ways in which you can create thread- safe classes. The simpler and more straightforward is to make them immutable, as shown in figure 6.5.

The fields of the following class are immutable (see how this might look for threads accessing it in fig- ure 6.6):

import net.jcip.annotations.Immutable; ...

@Immutable @Singleton public class MySafeObject{

private final Dependency dep1; private final Dependency dep2; @Inject

public MySafeObject(Dependency dep1, Dependency dep2) { this.dep1 = dep1;

this.dep2 = dep2; }

public Dependency getDep1() { return dep1; } public Dependency getDep2() { return dep2; } ...

}

Notice that MySafeObject has no setters and no methods that can alter or affect its dependencies’ state. This is good immutable design. The marker annotation @Immu- table is merely a tag that documents the behavior of MySafeObject (it has no effect in practice). Place it on classes that you have designed to be immutable. Don’t be con- fused by its proximity to @Singleton; it is there only as documentation. It helps unfa- miliar pairs of eyes read, understand, and reason about your classes. It also reduces confusion about intent. This is especially useful when you have bugs, since a colleague can spot the bug and know immediately that it violates design intent.

(immutable)

Thread #1

Thread #2

Thread #3

Figure 6.5 Multiple threads may safely use an immutable object.

TIP @Immutable and other thread-safety–related annotations are available from the Java Concurrency in Practice website.1 They are downloadable at the site and provided under a flexible open source license. Neither Guice nor any other injector or production library reacts to the @Immu- table annotation.

If their values never change, there is no risk of one thread modifying a value that’s out of sync with its rivals. As figure 6.6 shows, mutable single- tons, if not properly guarded, can get out of sync between threads.

With immutable objects, the order of thread execution is irrelevant, so you have fewer prob- lems to design for. Try very hard to make single- ton objects immutable. This is by far the simplest and best approach.

Of course, not every singleton can be made immutable. Sometimes you may need to alter the object’s state (for example, in a counter or accu- mulator). If you decide to use setter injection to resolve circular dependencies or the in-construc- tion problem (see chapter 3), you cannot make the class immutable. In these cases, you must carefully design the object and all of its dependencies to be

thread-safe. Here I have the same class; it is no longer immutable but thread-safe insofar as it guarantees that all of its dependencies will be visible to all threads:

import net.jcip.annotations.ThreadSafe;

@ThreadSafe @Singleton public class MySafeObject2{

private volatile Dependency dep1; private volatile Dependency dep2; @Inject

public void set(Dependency dep1, Dependency dep2) { this.dep1 = dep1;

this.dep2 = dep2; }

public Dependency getDep1() { return dep1; } public Dependency getDep2() { return dep2; } ...

}

@ThreadSafe, like @Immutable, is intended to convey information about the class’s design, nothing more. Java’s volatile keyword ensures that the value of a field is kept 1 Find out more at http://jcip.net. Chapter 9 has a lot of detailed information on concurrency and good class

and object design. Jump ahead if you are curious.

Thread #1 Thread #2 Thread #3 Thread #1’s unsynced copy Thread #3’s unsynced copy

Figure 6.6 All visibility bets are off with multiple threads accessing a mutable object.

coherent with all threads. In other words, when MySafeObject2’s dependencies are changed using the set() method, they are immediately visible to all threads. This is a very important point, and you will appreciate why when @ThreadSafe is contrasted with the following class:

import net.jcip.annotations.NotThreadSafe;

@NotThreadSafe @Singleton public class MyUnsafeObject { private Dependency dep1; private Dependency dep2; @Inject

public void set(Dependency dep1, Dependency dep2) { this.dep1 = dep1;

this.dep2 = dep2; }

public Dependency getDep1() { return dep1; }

public Dependency getDep2() { return dep2; } ...

}

MyUnsafeObject is critically flawed, because the Java language makes no guarantees that any thread other than the one that set its dependencies will see the dependencies (without additional synchronization). In other words, the memory between threads in non-final, non-volatile fields may be incoherent. The subtle reason has to do with the way Java Virtual Machines (JVM) are designed to manage threads. Threads may keep local, cached copies of non-volatile fields that can quickly get out of sync with one another unless they are synchronized correctly. We’ll study this problem and its solutions in greater detail in chapter 9.

Making a class thread-safe is often a difficult and involved process. No simple lit- mus test exists to say a class is completely thread-safe. Even the best of us can get tripped up when dealing with apparently simple threading and concurrency issues. Your best bet is to reason carefully about a class and its threading environment. Explore plenty of scenarios both common and atypical where a singleton may be used. Try to convince at least one other person of the safety of your code. Actively hunt for flaws; never assume they don’t exist. Always validate your assumptions. Test! Test! And test again until you’re satisfied.

Thread-safety problems can also present in other ways, particularly when dealing with dependencies that cross scoping boundaries. This is an especially tricky situation called scope widening, which we’ll explore in detail in the next section.