Summary
The single case pattern is one of the simplest design patterns, but for Java developers, it has many drawbacks. In this month's column, David Geary explores the single case pattern and how to deal with these flaws in the face of Multithreading (multithreading), Class loaders (Classloaders), and serialization (serialization).
A singleton pattern is suitable for situations where a class has only one instance, such as a window manager, a print buffer pool, and a file system, which are examples of prototypes. Typically, the types of those objects are accessed by different objects across a software system, so a global access pointer is required, which is known as the application of a single case pattern. Of course it is only when you are sure that you no longer need any more than one instance.
The single case pattern is intended to be of concern in the preceding paragraph. By using a single case pattern you can:
Make sure that only one instance of a class is built
Provides a global access pointer to an object
Allow multiple instances in the future without affecting a single instance class client
Although the single design pattern is the simplest design pattern shown in the figure below, it presents a number of flaws for unwary Java developers. This article discusses the single case patterns and reveals those flaws.
Note: You can download the source code for this article from resources.
Single case Mode
In design pattern, the author describes a singleton pattern by making sure that a class has only one instance and provides a global access pointer to it.
The following figure illustrates a class diagram of a single case pattern.
(Figure 1)
Class diagram of a single case pattern
As you can see in the previous illustration, this is not a complete part of the single example pattern. The Singleton class in this figure retains a static reference to the unique singleton instance and returns a reference to that instance from the static getinstance () method.
Example 1 shows the implementation of a classic single example pattern.
Example 1. Classic Single Case model
public class Classicsingleton {
private static Classicsingleton instance = null;
Protected Classicsingleton () {
//Exists only to defeat instantiation.
}
public static Classicsingleton getinstance () {
if (instance = null) {
instance = new Classicsingleton ();
} Return
instance
}
}
The implementation of the single example pattern in Example 1 is easy to understand. The Classicsingleton class maintains a static reference to a single singleton instance and returns that reference from the static Method GetInstance ().
About the Classicsingleton class, there are several places that interest us. First, Classicsingleton uses a well-known lazy instantiation to create a reference to that Singleton class; As a result, an instance of this singleton class is created until the getinstance () method is first invoked. This technique ensures that instances of singleton classes are created only when they are needed. Second, note that Classicsingleton implements a protected construction method so that the client cannot instantiate an instance of a Classicsingleton class directly. However, you will be surprised to find that the following code is completely legitimate:
Java code
public class Singletoninstantiator {public
singletoninstantiator () {
Classicsingleton instance = Classicsingleton.getinstance ();
Classicsingleton anotherinstance =
new Classicsingleton ();
...
}
}
Why the previous code fragment can create an instance of Classicsingleton without inheriting it and classicsingleton the constructor of the class is protected. The answer is that the protected construction method can be invoked by its subclasses and other classes in the same package. Because Classicsingleton and Singletoninstantiator are in the same package (the default package), the Singletoninstantiator method can create instances of Clasicsingleton.
There are two solutions in this case: one is that you can make Classicsingleton's construction method change private (private) so that only the Classicsingleton method can call it, but it also means that Classicsingleton cannot have subclasses. Sometimes this is a very agreeable solution, and if so, it would be a good idea to declare your single example to be final, to be clear and to have the compiler use some performance tuning options. Another solution is to place your singleton class in an external package so that classes in other packages (including the default package) cannot instantiate a singleton class.
The 3rd area of interest in Classicsingleton is that if a single instance is loaded by a different class loader, there may be instances of multiple singleton classes. The assumption is not remote access, for example, some servlet containers use a completely different class loader for each servlet, so that if two servlet accesses a singleton class, they all have their own instances.
4th, if Clasicsingleton implements the Java.io.Serializable interface, then instances of this class can be serialized and recovered. However, if you serialize an object of a singleton class and then recover multiple objects, you will have multiple instances of the Singleton class.
Finally, perhaps the most important point, is that the Classicsingleton class in Example 1 is not thread-safe. If two threads, we call them thread 1 and thread 2, call the Classicsingleton.getinstance () method at the same time, if thread 1 is advanced into the If block, then thread 2 is controlled, Then there will be two instances of Classicsingleton being created.
As you can see from the previous discussion, although the single case pattern is one of the simplest design patterns, implementing it in Java is not as simple as it might be. This article will then reveal the Java specification's consideration of the single example pattern, but first let's close water tower to see how you can test your single case class.
Test single case mode
Next, I use JUnit, which corresponds to log4j, to test the single class, which runs through the remainder of this article. If you are not familiar with JUnit or log4j, please refer to the relevant resources.
Example 2 is a case of a single pattern with JUnit test Example 1:
Example 2. A case of a single case pattern
Java code
Import Org.apache.log4j.Logger;
Import Junit.framework.Assert;
Import Junit.framework.TestCase;
public class Singletontest extends TestCase {
private Classicsingleton sone = null, stwo = NULL;
private static Logger Logger = Logger.getrootlogger ();
Public singletontest (String name) {
super (name);
}
public void SetUp () {
logger.info ("Getting singleton ...");
Sone = Classicsingleton.getinstance ();
Logger.info ("... got singleton:" + sone);
Logger.info ("Getting singleton ...");
Stwo = Classicsingleton.getinstance ();
Logger.info ("... got singleton:" + stwo);
}
public void Testunique () {
logger.info ("Checking singletons for Equality");
Assert.assertequals (true, sone = = Stwo);
}
The example calls Classicsingleton.getinstance () 22 times and stores the returned reference in the member variable. Method Testunique () checks the references to see if they are the same. Example 3 is the output of this test case:
Example 3. Is the output of this test case
Java code
Buildfile:build.xml
init:
[echo] Build 20030414 (14-04-2003 03:08)
compile:
run-test-text:
[ Java]. INFO main: [B]getting Singleton ... [/b]
[Java] Info main: [b]created singleton:[/b] singleton@e86f41
[Java] info main: ... got singleton:singleton@e86f41
[ Java] INFO main: [B]getting Singleton ... [/b]
[Java] INFO main: ... got singleton:singleton@e86f41
[java] INFO main:checking singletons for Equality
[Java] time:0.0
[Java] OK (1 test)
As the previous listing shows, the simple test in Example 2 goes smoothly through the reference to the two single instance classes obtained through the----() classicsingleton.getinstance (); However, you should know that these references are obtained in a single thread. The following sections focus on testing a single instance class with multithreading.
considerations of multithreaded factors
The Classicsingleton.getinstance () method In Example 1 is not thread-safe because of the following code:
Java code
1:if (Instance = = null) {
2: instance = new Singleton ();
3:}
If a thread switches before the assignment statement in the second row, the member variable instance is still null, and then another thread may then enter into the IF block. In this case, two different singleton class instances are created. Unfortunately, this assumption rarely occurs, and this assumption is also difficult to appear during the test (in this case, it may be that the author feels lament to be unable to test the situation and thus cause people to relax their vigilance). To illustrate this thread rotation, I have to implement the class in Example 1 again. Example 4 is the revised single example class:
Example 4. The way of human arrangement
Java code
Import Org.apache.log4j.Logger;
public class Singleton {private static Singleton Singleton = null;
private static Logger Logger = Logger.getrootlogger ();
private static Boolean firstthread = true;
Protected Singleton () {//Exists only to defeat instantiation.
public static Singleton getinstance () {if (Singleton = null) {simulaterandomactivity ();
Singleton = new Singleton ();
} logger.info ("Created Singleton:" + singleton);
return singleton; private static void Simulaterandomactivity () {try {if (firstthread) {Firstthread = False
;
Logger.info ("Sleeping ...");
This is nap should give the second thread enough time
Thread.CurrentThread (). Sleep (50);
} catch (Interruptedexception ex) {Logger.warn ("Sleep Interrupted"); }
}
}
In addition to the single example class in this list enforces a multithreaded error handling, example 4 is similar to the single instance class in Example 1. When the getinstance () method is invoked for the first time, the thread that calls the method sleeps 50 milliseconds so that another thread also has time to invoke getinstance () and create a new singleton class instance. When the dormant thread wakes up, it also creates a new singleton class instance, so that we have two singleton class instances. Although example 4 is man-made, it simulates the real situation where the first thread invokes the getinstance () and is switched when it is not finished.
Example 5 tests the single example class of Example 4:
Example 5. Failed tests
Java code
Import Org.apache.log4j.Logger;
Import Junit.framework.Assert;
Import Junit.framework.TestCase;
public class Singletontest extends TestCase {private static Logger Logger = Logger.getrootlogger ();
private static Singleton Singleton = null;
Public singletontest (String name) {super (name);
public void SetUp () {singleton = null;
public void Testunique () throws Interruptedexception {//Both threads call Singleton.getinstance (). Thread threadone = new Thread (new Singletontestrunnable ()), threadtwo = new Thread (new singletontestrunnable
());
Threadone.start ();
Threadtwo.start ();
Threadone.join ();
Threadtwo.join (); } private static Class Singletontestrunnable implements Runnable {public void run () {//Get a refer
ence to the Singleton.
Singleton s = singleton.getinstance (); Protect Singleton member variable from//Multithreaded access. Synchronized (Singletontest.class) {if (singleton = null)//If local reference is null ... si Ngleton = s; ... set it to the singleton}//local reference must is equal to the one and/Only Inst Ance of Singleton;
Otherwise, we have two//Singleton instances.
Assert.assertequals (true, s = = singleton);
}
}
}
The example 5 test case creates two threads and then starts each one and waits for completion. This case maintains a static reference to a single instance class, and each thread invokes Singleton.getinstance (). If the static member variable is not set, the first thread will set it to the reference obtained by calling getinstance (), and then the static variable will compare to a local variable for equality.
A series of things happens when the test case runs: The first thread calls getinstance (), enters the If block, and then sleeps, then the second thread calls getinstance () and creates an instance of the Singleton class. The second thread sets the reference that this static member variable creates for it. The second thread checks the equality of this static member variable with a local backup. Then the test passes. When the first thread wakes up, it also creates an instance of a singleton class, and it does not set that static member variable (because the second thread has already been set), so the static variable is out of sync with that local variable, and the equality test fails. Example 6 lists the output of example 5:
Example 6, example 5 output
Java code
Buildfile:build.xml
init:
[echo] Build 20030414 (14-04-2003 03:06)
compile:
run-test-text:
INFO Thread-1: Sleeping
... Info Thread-2: Created SINGLETON:SINGLETON@7E5CBD
info Thread-1: Created Singleton:singleton@704ebb
Junit.framework.AssertionFailedError:expected:but was: in
junit.framework.Assert.fail (assert.java:47)
at Junit.framework.Assert.failNotEquals (assert.java:282) at
junit.framework.Assert.assertEquals (Assert.java :) at
junit.framework.Assert.assertEquals (assert.java:149) at
junit.framework.Assert.assertEquals ( assert.java:155) at
Singletontest$singletontestrunnable.run (Unknown Source) at
Java.lang.Thread.run ( thread.java:554)
[Java].
[Java] time:0.577
[Java] OK (1 test)
So far we know that example 4 is not thread-safe, so let's see how to fix it.
Sync
It is easy to make a single class of example 4 thread-safe----just like the following synchronized getinstance () method:
Java code
Public synchronized static Singleton getinstance () {
if (Singleton = = null) {
simulaterandomactivity ();
Singleton = new Singleton ();
}
Logger.info ("Created Singleton:" + singleton);
return singleton;
}
After synchronizing the getinstance () method, we can get the following results returned by the example 5 test case:
Java code
Buildfile:build.xml
init:
[echo] Build 20030414 (14-04-2003 03:15)
compile:
[Javac] Compiling 2 Source Files
run-test-text:
INFO Thread-1: Sleeping ...
Info Thread-1: Created singleton:singleton@ef577d
info Thread-2: Created singleton:singleton@ef577d
[Java] .
[Java] time:0.513
[Java] OK (1 test)
This, the test case works fine, and multithreading troubles are resolved; however, astute readers may realize that the getinstance () method only needs to be synchronized when the first time it is invoked. Because the performance overhead of synchronization is expensive (synchronization methods can be reduced to around 100 times than asynchronous methods), perhaps we can introduce a performance improvement method that only synchronizes assignment statements in the getinstance () method of a single instance class.
a method of improving performance
When looking for a performance improvement method, you might choose to rewrite the getinstance () method as follows:
Java code
public static Singleton getinstance () {
if (Singleton = null) {
synchronized (singleton.class) {
Singleton = New Singleton ();
}
return singleton;
}
This code fragment synchronizes only key code, rather than synchronizing the entire method. However, this code is not thread safe. Consider the following assumption: Thread 1 goes into the sync block, and the frontline 1 that it assigns values to the singleton member variable is switched. Then another thread enters the IF block. The second thread waits until the first thread completes, and still gets two different singleton class instances. Is there a way to fix this problem? Please read it down.
Double Lock Check
At first glance, double lock checking appears to be a technique for making lazy instances of thread-safe. The following code fragment shows this technique:
Java code
public static Singleton getinstance () {
if (Singleton = null) {
synchronized (singleton.class) {
if ( Singleton = = null) {
singleton = new Singleton ()
;
}}} return singleton;
}
What happens if two threads access the getinstance () method at the same time. Imagine that a thread 1 synchronized block is immediately switched. Next, the second thread enters the IF block. When thread 1 exits the synchronization block, thread 2 will re-examine to see if the singleton instance is still null. Because thread 1 sets the singleton member variable, the second check of thread 2 fails and the second singleton class instance is not created. That seems to be the case.
Unfortunately, double lock checking does not guarantee normal work, because the compiler will assign a value to singleton before the Singleton constructor is invoked. If the forward 1 is switched after the singleton reference is assigned, thread 2 is returned with a reference to an uninitialized singleton class instance.
A single example pattern implementation for improved thread safety
Example 7 lists a simple, fast, and thread-safe Single example schema implementation:
Example 7. A simple single case class
Java code
public class Singleton {public
final static Singleton INSTANCE = new Singleton ();
Private Singleton () {
//Exists only to defeat instantiation.
}
}
This code is thread-safe because static member variables must be created when the class is first accessed. You get a thread-safe implementation that automatically uses a lazy instantiation; you should use it this way:
Java code
Singleton Singleton = singleton.instance;
Singleton.dothis ();
Singleton.dothat ();
...
Of course everything is not perfect, the front of the singleton is only a compromise solution; If you use that implementation, you cannot change it so that later you may want to allow instances of multiple singleton classes. Implemented with a more mournful singleton pattern (using a getinstance () method to obtain an instance) you can change this method to return a unique instance or one of hundreds of instances. You cannot do this with a public static member variable.
You can safely use the example 7 single mode implementation or the implementation of example 1 with a synchronous getinstance () method. However, we have to look at another problem: you have to specify this single example class at compile time, which is not very flexible. The Registry of a single instance class allows us to specify a single instance class at run time.
using the Registry
Use a single example class registry to:
To specify a single instance class at run time
Prevent instances from producing multiple singleton class subclasses
In the single instance class of Example 8, a single instance class registry that is registered through the class name is maintained:
Example 8 single case class with registry
Java code
Import Java.util.HashMap;
Import Org.apache.log4j.Logger;
public class Singleton {private static HashMap map = new HashMap ();
private static Logger Logger = Logger.getrootlogger (); Protected Singleton () {//Exists only to thwart instantiation} public static synchronized Singleton
Stance (String classname) {if (classname = null) throw new IllegalArgumentException ("illegal classname");
Singleton Singleton = (Singleton) map.get (classname);
if (singleton!= null) {Logger.info ("Got Singleton from Map:" + singleton);
return singleton;
} if (Classname.equals ("Singeltonsubclass_one")) singleton = new Singletonsubclass_one ();
else if (classname.equals ("Singeltonsubclass_two")) singleton = new Singletonsubclass_two ();
Map.put (classname, Singleton);
Logger.info ("Created Singleton:" + singleton);
return singleton; }//ASsume functionality follows that ' s attractive to inherit}
The base class of this code first creates instances of subclasses and stores them in a map. But the base class has to pay a high price because you have to replace its getinstance () method for each subclass. Fortunately, we can use reflection to handle this problem.
using Reflection
In Example 9, the Singleton class with the registry uses reflection to instantiate an object of a particular class. In contrast to Example 8, the Singleton.getinstance () method does not need to be overridden in each of the implemented subclasses.
Example 9 using reflection to instantiate a single instance of a class
Java code
Import Java.util.HashMap;
Import Org.apache.log4j.Logger;
public class Singleton {private static HashMap map = new HashMap ();
private static Logger Logger = Logger.getrootlogger (); Protected Singleton () {//Exists only to thwart instantiation} public static synchronized Singleton
Stance (String classname) {Singleton Singleton = (Singleton) map.get (classname);
if (singleton!= null) {Logger.info ("Got Singleton from Map:" + singleton);
return singleton;
try {singleton = (singleton) class.forname (classname). newinstance ();
catch (ClassNotFoundException cnf) {logger.fatal ("couldn ' t find class" + classname);
catch (Instantiationexception IE) {logger.fatal ("couldn ' t instantiate an object of type" + classname);
The catch (Illegalaccessexception ia) {logger.fatal ("couldn ' t access class" + classname); } map.put (classname, Singleton);
Logger.info ("Created Singleton:" + singleton);
return singleton;
}
}
The Registry of a single class should indicate that they should be encapsulated in their own classes for maximum reuse.
encapsulating the Registration form
Example 10 lists a single instance of the Registry class.
Example 101 Singletonregistry Class
Java code