Singleton是指仅仅被实例化一次的类。Singleton通常被用来代表一个无状态的对象,如函数,或者那些本质上唯一的系统组件。
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。单例模式有5种(懒汉模式/饿汉模式/双锁检测模式/内部类/枚举类)。
实现Singleton有两种常见的方法。这两种方法都要保持构造器为私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例
第一种方法,公有静态成员是一个final域:
package com.yhjt.singleton;
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
//私有构造器只调用一次,用来初始化公有的静态final域Elvis.INSTANCE。
}
public void leaveTheBuilding(){
}
}
第一种方法的优点:
- API清楚的表示这个类是一个Singleton:公有的静态域是final的,所以该域总是包含相同的对象引用
- 简单。
但是享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要防止这种情况,可以修改构造器,让他们在被要求创建第二个实例的时候抛出异常。
第二种方法:共有的成员是静态工厂方法
package com.yhjt.singleton;
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
//对与静态方法Elvis.getInstance的所有调用,都会返回同一个对象引用。
public static Elvis getInstance(){
return INSTANCE;
}
public void leaveTheBuilding(){
}
}
当然这种方法也可能通过反射机制来创建第二个实例。
package com.yhjt.singleton;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) {
Elvis instance1 = Elvis.getInstance();
Elvis instance2 = Elvis.getInstance();
System.out.println(instance2==instance1);//true
//通过反射来获取一个不同的实例
Elvis instance3 = null;
Constructor[] constructors = instance1.getClass().getDeclaredConstructors();
AccessibleObject.setAccessible(constructors,true);
try {
instance3 = (Elvis) constructors[0].newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("instance1的地址"+instance1);//instance1的地址com.yhjt.singleton.Elvis@1540e19d
System.out.println("instance2的地址"+instance2);//instance2的地址com.yhjt.singleton.Elvis@1540e19d
System.out.println("instance3的地址"+instance3);//instance3的地址com.yhjt.singleton.Elvis@677327b6
}
}
第二种方法的优点:
- 灵活性。在不改变API的的前提下,我们可以改变改类是否应该为Singleton的想法。工厂方法返回的是一个唯一的实例,但是,它很容易被修改,比如修改为每个调用该方法的线程返回一个唯一的实例。
- 如果应用程序需要,可以写一个泛型Singleton工厂。
- 可以通过方法引用作为提供者,比如
Elvis::instance
就是一个Supplier<Elvis>
除了满足以上任意一种优势,否则还是优先考虑公有域方法
通过反序列化也可以获得一个新实例
// 通过反序列化来获取多个实例。
Elvis instance4 = null;
try (FileOutputStream fos = new FileOutputStream("test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(instance1);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"))) {
instance4 = (Elvis) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("instance4的地址:" + instance4);
为了维护并且保证Singleton,必须将我们的实例域都声明成瞬时的(transient),并且提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创造一个实例。修改后的Elvis:
package com.yhjt.singleton;
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
public static Elvis getInstance(){
return INSTANCE;
}
private Object readResolve() {
return INSTANCE;
}
public void leaveTheBuilding(){
}
}
实现Singleton的第三种方式是声明一个包含单个元素的枚举类型:
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
}
}
虽然这种方法没有广泛采用,但是单元素枚举类型经常成为实现单例模式的最佳方法,有下面几个优点:
- 代码更为简洁。
- 无偿的提供了序列化机制。
- 可以绝对的防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。
注意,如果Singleton必须扩展一个超类,而不是扩展Enum的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口)