Important concerns of this paper:
- Thread-safe singleton pattern
- Prevent object clones from breaking singletons
- Prevent serialization from breaking the singleton pattern
The singleton pattern
What is the singleton pattern
The singleton pattern is a creative type pattern that manages instances. The singleton pattern guarantees that there is at most one instance of a specified class in your application.
Application scenario of singleton mode
- Project Configuration class
Classes that read the configuration of a project can be singletons because they need to be read only once, and the configuration fields are generally more resource-efficient. This singleton class provides global access to the classes in your application. You do not need to read the configuration file multiple times.
- Application Logging
The Logger is ubiquitous in your application. It should also only be initialized once, but can be used everywhere.
- Analysis and reporting classes
If you’re using data Analytics tools like Google Analytics. Notice that they are designed to be singletons, initialized only once, and then used in every action of the user.
A class that implements the singleton pattern
-
Set the default constructor to private. Prevents other classes from initializing the class directly from the application.
-
Create a static method that is public static. This method is used to return a singleton class instance.
-
You can also choose lazy load initialization to be more friendly.
The sample code
See the following classes for sample code
- org.byron4j.cookbook.designpattern.singleton.Singleton
public class Singleton {
private static Singleton instance;
// Privatize the constructor
private Singleton(a){}// Provide static methods
public static Singleton getInstance(a){
// Lazy load initialization, creating the instance the first time it is used
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void display(a){
System.out.println("Hurray! I am create as a Singleton!"); }}Copy the code
Unit test classes:
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(a){
final Set<Singleton> sets = new HashSet<>();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(a){ Singleton s = Singleton.getInstance(); sets.add(s); }}); } System.out.println(sets); }}Copy the code
The output is as follows, resulting in multiple Singleton instances:
[org.byron4j.cookbook.designpattern.singleton.Singleton@46b91344, org.byron4j.cookbook.designpattern.singleton.Singleton@1f397b96]
Thread-safe singleton pattern
Thread safety is very important for singleton classes. The Singleton class described above is not thread-safe, as it is possible to create multiple Singleton instances in a concurrent thread scenario.
To circumvent this problem, we can modify the getInstance method with the synchronized word, which forces the thread to wait until the previous thread completes execution, thus avoiding multiple threads accessing the method at the same time.
public static synchronized Singleton getInstance(a) {
// Lazy initialization, creating object on first use
if (instance == null) {
instance = new Singleton();
}
return instance;
}
Copy the code
This does solve the thread-safety problem. However, the synchronized keyword has serious performance problems. We can further optimize the getInstance method to synchronize the instances and narrow the scope of the method:
public static Singleton getInstance(a) {
// Lazy initialization, creating object on first use
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = newSingleton(); }}}return instance;
}
Copy the code
Unit test time comparison of three methods:
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(a){
final Set<Singleton> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(a){ Singleton s = Singleton.getInstance(); sets.add(s); }}); } System.out.println("When the test used." + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testSynchronized(a){
final Set<SingletonSynchronized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(a){ SingletonSynchronized s = SingletonSynchronized.getInstance(); sets.add(s); }}); } System.out.println("When testSynchronized used." + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testOptimised(a){
final Set<SingletonSynchronizedOptimized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(a){ SingletonSynchronizedOptimized s = SingletonSynchronizedOptimized.getInstance(); sets.add(s); }}); } System.out.println("When testOptimised used."+ (System.currentTimeMillis() - startTime)); System.out.println(sets); }}Copy the code
Run the test case with the following output:
The test is:1564
[org.byron4j.cookbook.designpattern.singleton.Singleton@68Eae58e] testSynchronized3658
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized@36429A46] stewed with water2254
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized@21571826]
Copy the code
As you can see, the initial implementation has the best performance, but is not thread-safe; Synchronized locks the entire getInstance method, which is thread-safe, but has the worst performance; Narrow Synchronized to improve performance.
Singleton and object clone
The correct use of the Clone method is also important when it comes to singleton classes:
package org.byron4j.cookbook.designpattern.singleton;
/** * singleton instances * 1. Privatize constructors * 2. Provide static methods for external fetching singleton instances * 3. Delay initialization of instances */
public class SingletonZClone implements Cloneable{
private static SingletonZClone instance;
// Privatize the constructor
private SingletonZClone(a){}// Provide static methods
public static SingletonZClone getInstance(a){
// Reduce the performance loss by reducing the synchronization lock range
if(instance == null) {synchronized (SingletonZClone.class){
if(instance == null){
instance = newSingletonZClone(); }}}return instance;
}
/** * clone method -- change to public *@return
* @throws CloneNotSupportedException
*/
@Override
public Object clone(a) throws CloneNotSupportedException {
return super.clone();
}
public void display(a){
System.out.println("Hurray! I am create as a SingletonZClone!"); }}Copy the code
By default, clone protected modifier is changed to public modifier. For testing purposes, for example:
@Test
public void testClone(a) throws CloneNotSupportedException {
SingletonZClone singletonZClone1 = SingletonZClone.getInstance();
SingletonZClone singletonZClone2 = SingletonZClone.getInstance();
SingletonZClone singletonZClone3 = (SingletonZClone)SingletonZClone.getInstance().clone();
System.out.println(singletonZClone1 == singletonZClone2);
System.out.println(singletonZClone1 == singletonZClone3);
System.out.println(singletonZClone2 == singletonZClone3);
}
Copy the code
The output is as follows:
true
false
false
The clone object may have the same attribute value, but it is not the same object instance.
x.clone() ! = x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
The clone method returns a copy of an instance of the cloned object. The values are the same except for the memory address, so the clone and the cloned object are not the same instance. It can be seen that the Clone method destroys the singleton class. In order to prevent this problem, we need to disable the Clone method and directly change it to:
/** * clone method -- change to public *@return
* @throws CloneNotSupportedException
*/
@Override
public Object clone(a) throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Copy the code
Singleton and serialization problems
The Java serialization mechanism allows an object’s state to be converted into a byte stream that can be easily stored and transferred. Once the object is serialized, you can deserialize it — turning byte streams into objects. If a Singleton class is serialized, duplicate objects may be created. We can use hook hook to explain this problem.
ReadResolve () method
The readResolve() method is described in the Java specification:
For serializable and externalized classes, the readResolve() method allows a class to replace/resolve objects read from the stream. By implementing the readResolve method, a class has direct control over deserialized instances and types. The definition is as follows:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; Copy the code
The readResolve method is called when ObjectInputStream reads an object from the stream. ObjectInputStream checks if the class defines a readResolve method. If the readResolve method is defined, it is called to specify the result object returned after deserialization from the stream. Return the same type as the original object or a ClassCastException will occur.
@Test
public void testSeria(a) throws Exception {
SingletonZCloneSerializable singletonZClone1 = SingletonZCloneSerializable.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(singletonZClone1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializable test = (SingletonZCloneSerializable) ois.readObject();
ois.close();
System.out.println(singletonZClone1 == test);
}
Copy the code
Test output: false; This indicates that the deserialization is not the original instance, which will break the singleton pattern.
So we can override the readResolve method to solve the serial-breaking singleton problem:
Class SingletonZCloneSerializableReadResolve increase readResolve method:
/** * return instance when deserializing to prevent breaking singleton pattern *@return* /
protected Object readResolve(a){
return getInstance();
}
Copy the code
Execute test cases:
@Test
public void testSReadResolve(a) throws Exception {
s = SingletonZCloneSerializableReadResolve.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(s);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializableReadResolve test = (SingletonZCloneSerializableReadResolve) ois.readObject();
ois.close();
System.out.println(s == test);
}
Copy the code
Printing true effectively prevents deserialization from destroying singletons.
You know what?
-
Singleton classes are rarely used, and if you are going to use this design pattern, you must know exactly what you are doing. Because only one instance is created globally, there is a risk in resource-constrained platforms.
-
Pay attention to object cloning. The singleton pattern requires careful checking and blocking of the Clone method.
-
In multi-threaded access, attention should be paid to thread safety.
-
Beware of multiple classloaders, which might break your singleton classes.
-
If the singleton class is serializable, you need to implement strict typing