点击上方蓝字关注我们
Singleton 指仅仅被实例化一的类,通常用于代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。
使类成为Singleton 会使它的客户端测试变得困难,因为无法给Singleton 替换模拟实现,除非它实现一个充当其类型的接口。
实现Singleton 共有3种方法,在Java 1.5发行版之前实现有两种;在Java 1.5发行版之后实现Singleton 还有第三种方法,下面进行依次介绍。
第一种:公有静态类是个final域
构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例。
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
//构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例
private Elvis(){
//...
}
public void leaveTheBuilding(){
//...
}
}
私有构造器仅被调用依次,用来实例化公有静态final域 Elvis.INSTANCE。由于缺少共有的或者受保护的构造器,所以保证了Elvis 全局唯一性。
但要注意一点:享有的客户端可以借助 AccessiableObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,代码里面做个检测判断,在被要求创建第二个实例时抛出异常。
第二种:公有成员是个静态工厂方法
实现Singleton 的第二种方法,公有的成员是个静态工厂方法:
public class Elvis2 {
// 私有成员
private static final Elvis2 INSTANCE = new Elvis2();
// 静态方法公有成员
public static Elvis2 getInstance(){
return INSTANCE;
}
//构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例
private Elvis2(){
//...
}
public void leaveTheBuilding(){
//...
}
}
对于静态方法Elvis2.getInstance() 的所有调用,都会返回一个对象引用,所以永远不会创建其他实例(但是反射机制依然可以绕开这个设计,因此最好构造器同样做个判断)。
公有域方法的好处是,组成类成员的声明,很清楚的表明这个类是一个Singleton。公有的静态域是final的, 所以该域总是包含相同的对象引用。
但公有域方法在性能上不再有任何优势了,因为现代的JVM 实现几乎都能够将静态工厂方法的调用内联化(Inline Method)。
另外,工厂方法有2个优势:
-
在不改变API的前提下,我们可以改变此类是否应该为Singleton 的想法。工厂方法返回该类的唯一实例,但会容易被修改,比如改为每个调用该方法的线程返回一个唯一的实例。
-
第二个优势,与泛型有关。为了让Singleton 类变成可序列化的(Serializable),仅仅在对象声明中加上“implements Serializable” 是不够的。为了维护并保证Singleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResolve方法。否则每次反序列化都会创建一个新实例。
// readResolve method to preserve singleton property
private Object readObject(){
// Return the one true Elvis and let the garbage collector take care of the Elvis impersonator
return INSTANCE;
}
第三种:包含单个元素的枚举类型
第三种实现Singleton 的方法是编写一个包含单个元素的枚举类型:
public class SingletonCase6 {
enum SingletonEnum {
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private SingletonCase6 singleton;
//私有化枚举的构造函数
private SingletonEnum() {
System.out.println("-----1 init constructor-----");
singleton = new SingletonCase6();
}
public SingletonCase6 getSingleton() {
System.out.println("-----2 return singleton-----");
return singleton;
}
}
private SingletonCase6() {
}
public static SingletonCase6 getInstance(){
System.out.println("-----3 getInstance -----");
return SingletonEnum.INSTANCE.getSingleton();
}
public static void main(String[] args) {
getInstance();
}
}
输出结果:
-----3 getInstance -----
-----1 init constructor-----
-----2 return singleton-----
这种方法在功能上跟公有域方法相近,但它更加简洁,无偿的提供了序列化机制,绝对防止多次实例化,即使是面对复杂的序列化或者反射攻击的时候。
总结
上面的第三种方法元素的枚举类型已经成为实现Singleton 最佳方法。
单例模式实际上有5种(懒汉模式/饿汉模式/双锁检测模式/内部类/枚举类),相关的单例模式参考文章:
-
设计模式学习(五):单例模式 (上)
https://blog.csdn.net/qq_29166327/article/details/82229348?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160351095319195264762715%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=160351095319195264762715&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v1~rank_blog_v1-3-82229348.pc_v1_rank_blog_v1&utm_term=%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F&spm=1018.2118.3001.4187
-
设计模式学习(五):单例模式 (下)
https://blog.csdn.net/qq_29166327/article/details/103587276?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160351095319195264762715%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=160351095319195264762715&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v1~rank_blog_v1-4-103587276.pc_v1_rank_blog_v1&utm_term=%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F&spm=1018.2118.3001.4187
—END—
扫描二维码
获取技术干货
后台技术汇
点个“在看”表示朕
已阅