概念
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类
为什么要使用单例
有些数据在系统中只应该保存一份,当使用多个对象时会造成一些错误。有一些资源访问冲突时也可以使用单例模式来解决。
实现方式
由于对象只能有一个,所以构造函数的权限必须是private
私有的,这样外部就无法通过new
来创建实例
还有一些需要关注的点
- 创建对象的线程安全问题
- 是否支持延迟加载
- 获取实例方法的性能是否高
饿汉式
优点:写法简单。在类装载时就完成了实例化。线程安全
缺点:由于在类装载时就完成了实例化,如果从始至终都没有用到这个实例,会造成内存浪费
class Single {
/**
* 设置成私有的,保证无法在其它类中使用 new 关键字进行实例化
*/
private Single() {}
private final static Single INSTANCE = new Single();
/**
* 从外界获取实例的唯一方法,从这个方法拿到的实例肯定是全局唯一的
* @return
*/
public static Single getInstance() {
return INSTANCE;
}
}
/**
* 与上面的类似,用了静态代码块,不同的代码写法其他完全一样
*/
class SingleTest2 {
private SingleTest2() {}
private static SingleTest2 SINGLE_TEST_2;
static {
SINGLE_TEST_2 = new SingleTest2();
}
public static SingleTest2 getInstance() {
return SINGLE_TEST_2;
}
}
懒汉式
在需要用到实例时才进行初始化,所以不会造成内存浪费,但多线程情况下会出现多个实例的情况
/**
* 懒汉式的单例模式
* 线程不安全
*/
class UnSafeSingle {
private UnSafeSingle() {
}
private static UnSafeSingle INSTANCE;
public static UnSafeSingle getInstance() {
if (INSTANCE == null) {
//这个地方,当多个线程同时进入判断后会造成多个实例
INSTANCE = new UnSafeSingle();
}
return INSTANCE;
}
}
/**
* 通过加锁的方式实现线程安全的懒汉式单例
* 由于获取单例的方法每次都需要进行同步,所以效率不高
*/
class SynchronizedSingle {
private SynchronizedSingle() {
}
private static SynchronizedSingle INSTANCE;
public static synchronized SynchronizedSingle getInstance() {
if (INSTANCE == null) {
//由于方法加锁,所以每次只能一个线程访问,不会造成多个实例的情况,但是效率不高
INSTANCE = new SynchronizedSingle();
}
return INSTANCE;
}
}
/**
* 双重检查
* 当使用getInstance()方法时不会进行同步,先进行判断。
* 由于使用了 volatile 关键字,即便有两个线程同时执行了这个方法,只要有一个线程对变量进行了修改,另外一个线程也能接收到更改的信号
*/
class SafeSingle{
private SafeSingle(){}
private static volatile SafeSingle INSTANCE;
public static SafeSingle getInstance(){
if(INSTANCE==null){
//使用了双重检查
synchronized (SafeSingle.class){
if(INSTANCE==null){
INSTANCE = new SafeSingle();
}
}
}
return INSTANCE;
}
}
静态内部类
使用了静态内部类,在外部类方法调用时才会进行初始化也就实现了懒加载,并且在类加载过程中也是线程安全的,不会出现多个实例的情况. 由JVM来保证了类的唯一性.
|
|
枚举
枚举能避免多线程产生多个实例的问题,还能防止反序列化重新创建新的对象
|
|
单例存在的问题
大部分情况下, 在项目中使用单例来表示一个全局唯一类, 比如配置信息, ID生成器类.
但是它也存在一些问题:
-
单例对OOP特性的支持不友好
OOP的四大特性是封装, 抽象, 继承, 多态. 单例对抽象, 继承, 多态都支持的不是很好.
假如我们有一个IdGenerator的类来生成一个全局唯一ID. 如果我们使用单例模式, 就会违背了基于接口而非实现的设计原则, 也就违背了抽象特性. 如果我们需要更改不同业务的ID生成算法, 就需要修改所有用到IdGenerator的地方
-
单例会隐藏类之间的依赖关系
由于单例不需要显示创建, 不需要依赖参数传递, 可以在函数中直接调用. 所以在代码复杂的情况下, 这种调用关系就会非常隐蔽
-
单例对代码的扩展性不友好
单例类只能有一个对象实例, 如果我们需要在代码中创建两个实例或多个实例, 就需要对代码有比较大的改动
-
单例对代码的可测试性不友好
-
单例不支持有参数的构造函数