- 关于如何创建一个独一无二的实例对象
文章说明
- 该文章为
《Head First 设计模式》
的学习笔记 - 非常推荐大家去买一本
《Head First 设计模式》
去看看,节奏轻松愉悦,讲得通俗易懂,非常适合想要学习、了解、应用设计模式以及OO设计原则的小白。
经典案例
// 这种方式是非线程安全的
public class Singleton {
// 静态:确保该类只有一个单例对象
private static Singleton uniqueInstance;
// 私有的:确保外部类不能调用该构造函数
private Singleton() {}
// 延迟实例化
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 其他有用的方法
}
延迟实例化
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
- 当我们不需要这个实例,它就永远不会产生
- 访问
getInstance()
方法与访问全局变量一样简单,只是单件可以延迟实例化
要点
-
擅长的事
-
- 用来管理共享的资源,例如
数据库连接
或者线程池
实例化
- 用来管理共享的资源,例如
-
- 外部类为了得到实例,必须通过
全局访问点
请求得到一个实例,而不是自行实例化得到一个实例
- 外部类为了得到实例,必须通过
-
- 该实例有可能是这次调用时被创建出来的,也有可能是以前很早就创建出来的。
定义单件模式
单件模式确保了一个类只有一个实例,并提供一个全局访问点
三元素
- 利用一个私有的静态成员记录唯一实例
- 声明私有的构造器
- 声明一个公开的静态方法用来创建或获取唯一的实例对象,提供全局访问点
类图
关于线程安全
- 上例的经典单件模式有线程安全问题。即当使用多线程获取唯一实例的时候,有可能创建出不同的对象,这样的隐患是很严重的
处理多线程问题
- 使用传统的
同步(synchronized)
方法
// 使用同步(synchronized)方法实现线程安全
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
// 延迟实例化
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
- 事实上,只有首次调用才会实例化唯一对象,所以同步方法也只有首次调用才起作用,这意味着之后的调用不需要同步,这种实现是一种性能的浪费。
问题:性能大幅度下降。
优点:简单直接。
适用性:没有性能考虑,没有更好的实现。
- 使用"急切"实例化的方式,而不用延迟实例化的做法
// 使用急切实例化实现线程安全
public class Singleton {
// 急切实例化
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static synchronized Singleton getInstance() {
return uniqueInstance;
}
}
- 依赖了
JVM
,它保证了任何线程访问uniqueInstance
静态变量之前,一定创建该实例。
问题:有可能频繁地在运行时创建对象。
优点:性能有所提升。
适用性:在创建和运行时压力不大时
- 使用双重检查加锁,在
getInstance()
使用同步代码块
,减少同步区域
// 双重检查加锁实现线程安全
public class Singleton {
// volatile: 确保在使用多线程时正确地处理该静态变量
private static volatile Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(uniqueInstance == null) // 检查
synchronized(Singleton.class) {
if(uniqueInstance == null) // 二次检查
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
- 只有第一次会同步,这才是我们想要的
问题:不适用于1.4及更早版本的Java。
优点:大大提高性能。
适用性:Java5以上的版本,杀鸡用牛刀
要点
-
是否可以使用类的单件
-
- 如果我们的类自给自足,而且不依赖于复杂的初始化,可以。
-
- 如果初始化过于复杂,牵涉到的类很多,那么就有可能产生一些微妙的,有关类初始化次序的bug
关于类加载器
-
- 每个类加载器都有自己的命名空间,如果有两个以上的类加载器,不同的类加载器可能加载同一个类,也就是说,同一个类被加载多次。
-
- 如果程序使用了
多个类加载器
的同时使用了单件模式
,要小心注意。
- 如果程序使用了
-
- 解决方案:自行指定类加载器,并指定同一个类加载器。
单件的使用
-
- 不要滥用使用了单件模式。适合单件模式的机会不多。
关于全局变量
-
- 全局变量基本上是对对象的静态引用
-
- 用许多全局变量指向许多小对象会造成命名空间的污染