为什么要使用单例?
单例存在哪些问题?
单例与静态类的区别?
有何替代的解决方案?
单例设计模式:一个类只允许创建一个对象(或者实列),那这个类就是单例类,这种设计模式就叫作单例设计模式,简称单例模式
单例的用处
从业务概念上,有些数据在系统中只应保存一份,就比较适合设计单例类。除此之外,我们还可以使用单例解决资源访问冲突问题。
如何实现单例模式?
实现单例模式的关注点如下::
- 构造函数需要private访问权限符修饰,这样才能避免被外部通过new 实列化
- 需要考虑对象创建的线程安全问题
- 考虑是否是要延迟加载
- 考虑getInstance()性能是否高(是否加锁)。
单例模式的实现方式如下
1、饿汉模式
在类加载的时候,instance静态实例就已经创建并初始化好。所以instance是线程安全的。但这种方式不支持延迟加载。
如果实例资源占用多或初始化耗时长,采用饿汉模式虽然会浪费一定的资源,避免程序运行之后,影响程序的性能。如果存在问题,或者资源不够,问题也能及时暴露及时解决。
/**
* 唯一递增的ID生成器
*
* 单例设计模式--饿汉模式
*/
public class IdGeneratorOfHungryMode {
private AtomicLong id =new AtomicLong(100);
private static final IdGeneratorOfHungryMode instance=new IdGeneratorOfHungryMode();
private IdGeneratorOfHungryMode(){}
public static IdGeneratorOfHungryMode getInstance(){
return instance;
}
public long getId(){
return id.getAndIncrement();
}
}
2、懒汉模式
懒汉模式相对于饿汉模式来说,就是支持延迟加载。代码实现如下:
/**
* 唯一递增Id生成器
*
* 单例设计模式--懒汉模式
*/
public class IdGeneratorOfLazyMode {
private AtomicLong id=new AtomicLong(100);
private static IdGeneratorOfLazyMode instance;
private IdGeneratorOfLazyMode(){}
public static synchronized IdGeneratorOfLazyMode getInstance(){
if(instance==null){
instance=new IdGeneratorOfLazyMode();
}
return instance;
}
public long getid(){
return id.getAndIncrement();
}
}
懒汉模式如果考虑线程安全的话,需要给getInstance()方法加锁。如果频繁调用的话,就会存在频繁的加锁、释放锁以及低并发等问题,会存在性能问题。
3、双重检测模式
双重检测模式是一种支持延迟加载,有支持高并发的单例设计模式。代码实现如下:
/**
* 唯一递增id生成器
*
* 单例设计模式--双重检测
*/
public class IdGeneratorOfTwoFoldTest {
private AtomicLong id = new AtomicLong(100);
private static IdGeneratorOfTwoFoldTest instance;
private IdGeneratorOfTwoFoldTest(){}
public IdGeneratorOfTwoFoldTest getInstance(){
if(instance==null){
synchronized (IdGeneratorOfTwoFoldTest.class){
if(instance==null){
instance=new IdGeneratorOfTwoFoldTest();
}
}
}
return instance;
}
public long getId(){
return id.getAndIncrement();
}
}
这种实现方式中只要instance被创建之后,即使在调用getInstance()方法也不会进入到加锁逻辑当中。这就是它支持高并发的原因
4、静态内部类
这是一种及支持延迟加载,又是线程安全的一种实现方式,代码实现如下
/**
* 唯一递增id生成器
*
* 单例模式--静态内部类
*/
public class IdGeneratorOfStaticInnnerClass {
private AtomicLong id = new AtomicLong(100);
private IdGeneratorOfStaticInnnerClass(){}
private static class SingletonHolder{
private static final IdGeneratorOfStaticInnnerClass instance=new IdGeneratorOfStaticInnnerClass();
}
public static IdGeneratorOfStaticInnnerClass getInstance(){
return SingletonHolder.instance;
}
public long getId(){
return id.getAndIncrement();
}
}
SingletonHolder是一个静态内部类,当外部IdGeneratorOfStaticInnerClass被加载的时候,并不会创建SingletonHolder的实例对象。只有当调用getInstance()方法的时候,SingletonHolder才会被加载,这个时候才会创建instance。instance是唯一的,也是线程安全的,都有JVM保证。
5、枚举
这是一种通过Java枚举本身特性,来保证线程安全和实例唯一性的一种创建方式。代码实现如下:
/**
* 唯一递增ID生成器
*
* 单例设计模式--枚举
*/
public enum IdGeneratorOfEnum {
INSTANCE;
private AtomicLong id=new AtomicLong(100);
public long getId(){
return id.getAndIncrement();
}
}
单例模式存在的问题?
- 对OOP的支持不友好
- 单例会隐藏依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单列不支持有参构造函数
虽然有这么多缺陷,但如果当前需求中有数据在系统中只保存一份,暂时对其又没有扩展需求,同时也不依赖外部系统,那就可以放心大胆的使用单列模式,如果后续需求要求对其进行扩展,再重构这部分代码也不迟。没必要为将来可能不存在的功能买单。
单例模式有什么替代解决方案?
保证全局唯一,除了单列模式,还可以使用静态方法来实现。但静态方法并不能解决上面存在的问题。如果要解决这些问题,我们需要从根本上寻找其他方式来实现全局唯一类。比如工厂模式、IOC容器等。还可通过程序员自己来保证(自己在编写代码的时候自己保证自己只创建一个对象)
参考:设计模式之美--王争