您的位置:首页 > 其它

2设计模式-单例模式

2018-02-27 16:18 92 查看

单例模式

定义:

确保某个类只有一个实例,能自行实例化并向整个系统提供这个实例。

所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。

使用场景:

当产生多个对象会消耗过多资源,比如IO和数据操作

某种类型的对象只应该有且只有一个,如,在多个功能模块里都需要写的log

一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。

实现方式

1、懒汉式,线程不安全

是否 Lazy 初始化:是。

是否多线程安全:否

实现难度:易

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class SimpleSingleton {
//1.static单例变量
private static SimpleSingleton instance;

//2.私有的构造方法
private SimpleSingleton() {

}

//3.静态方法为调用者提供单例对象
public static SimpleSingleton getInstance() {
if (instance == null) {//1
instance = new SimpleSingleton();//2
}
return instance;
}
}


在多线程高并发的情况下,这样写会有明显的问题,当线程A调用getInstance方法,执行到1时,检测到instance为null,于是执行2去实例化instance,当2没有执行完时,线程B又调用了getInstance方法,这时候检测到instance依然为空,所以线程B也会执行2去创建一个新的实例。这时候,线程A和线程B得到的instance就不是一个了,这违反了单例的定义。

2、饿汉式

是否 Lazy 初始化:否

是否多线程安全:是

实现难度:易

描述:这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

public class EHanSingleton {
//static final单例对象,类加载的时候就初始化
private static final EHanSingleton instance = new EHanSingleton();

//私有构造方法,使得外界不能直接new
private EHanSingleton() {
}

//公有静态方法,对外提供获取单例接口
public static EHanSingleton getInstance() {
return instance;
}
}


饿汉单例模式解决了多线程并发的问题,因为在加载这个类的时候,就实例化了instance。当getInstatnce方法被调用时,得到的永远是类加载时初始化的对象(反序列化的情况除外)。但这也带来了另一个问题,如果有大量的类都采用了饿汉单例模式,那么在类加载的阶段,会初始化很多暂时还没有用到的对象,这样肯定会浪费内存,影响性能,我们还是要倾向于1的做法,在首次调用getInstance方法时才初始化instance。

3、懒汉式,线程安全

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:易

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)

/**
* 懒汉模式
* @author josan_tang
*/
public class LanHanSingleton {
private static LanHanSingleton instance;

private LanHanSingleton() {

}

/**
* 增加synchronized关键字,该方法为同步方法,保证多线程单例对象唯一
*/
public static synchronized LanHanSingleton getInstance() {
if (instance == null) {
instance = new LanHanSingleton();
}
return instance;
}
}


与1的唯一区别在于getInstance方法前加了synchronized 关键字,让getInstance方法成为同步方法,这样就保证了当getInstance第一次被调用,即instance被实例化时,别的调用不会进入到该方法,保证了多线程中单例对象的唯一性。

- 优点:单例对象在第一次调用才被实例化,有效节省内存,并保证了线程安全。

- 缺点:同步是针对方法的,以后每次调用getInstance时(就算intance已经被实例化了),也会进行同步,造成了不必要的同步开销。==不推荐使用这种方式。==

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

getInstance() 的性能对应用程序很关键。

public class DCLSingleton {
//增加volatile关键字,确保实例化instance时,编译成汇编指令的执行顺序
private volatile static DCLSingleton instance;

private DCLS
4000
ingleton() {

}

public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
//当第一次调用getInstance方法时,即instance为空时,同步操作,保证多线程实例唯一
//当以后调用getInstance方法时,即instance不为空时,不进入同步代码块,减少了不必要的同步开销
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}


DCL失效:

在JDK1.5之前,可能会有DCL实现的问题,上述代码中的20行,在Java里虽然是一句代码,但它并不是一个真正的原子操作。

instance = new DCLSingleton();

它编译成最终的汇编指令,会有下面3个阶段:

给DCLSingleton实例分配内存

调用DCLSingleton的构造函数,初始化成员变量。

将instance指向分配的内存空间(这个操作以后,instance才不为null)

在jdk1.5之前,上述的2、3步骤不能保证顺序,也就是说有可能是1-2-3,也有可能是1-3-2。如果是1-3-2,当线程A执行完步骤3(instance已经不为null),但是还没执行完2,线程B又调用了getInstance方法,这时候线程B所得到的就是线程A没有执行步骤2(没有执行完构造函数)的instance,线程B在使用这样的instance时,就有可能会出错。这就是DCL失效。

在jdk1.5之后,可以使用volatile关键字,保证汇编指令的执行顺序,虽然会影响性能,但是和程序的正确性比起来,可以忽悠不计。

Java内存模型

优点:第一次执行getInstance时instance才被实例化,节省内存;多线程情况下,基本安全;并且在instance实例化以后,再次调用getInstance时,不会有同步消耗。

缺点:jdk1.5以下,有可能DCL失效;Java内存模型影响导致失效;jdk1.5以后,使用volatile关键字,虽然能解决DCL失效问题,但是会影响部分性能。

5、登记式/静态内部类

是否 Lazy 初始化:是

是否多线程安全:是

实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

==推荐使用==

public class StaticClassSingleton {
//私有的构造方法,防止new
private StaticClassSingleton() {

}

public static StaticClassSingleton getInstance() {
return StaticClassSingletonHolder.instance;
}

/**
* 静态内部类
*/
private static class StaticClassSingletonHolder {
//第一次加载内部类的时候,实例化单例对象
private static final StaticClassSingleton instance = new StaticClassSingleton();
}
}


第一次加载StaticClassSingleton类时,并不会实例化instance,只有第一次调用getInstance方法时,Java虚拟机才会去加载StaticClassSingletonHolder类,继而实例化instance,这样延时实例化instance,节省了内存,并且也是线程安全的。==这是推荐使用的一种单例模式。==
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  单例模式