使用场景
- 要求生成唯一序列号的环境
-
在整个项目中需要一个共享访问点或共享数据
例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的; -
创建一个对象需要消耗的资源过多
如要访问IO和数据库等资源; -
需要定义大量的静态常量和静态方法
如工具类,可以采用单例模式(当然,也可以直接声明为static的方式)。
定义
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。
单例模式的静态结构图如下:
单例模式结构图
优缺点
优点:
-
内存开支小
由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显了。 -
提高系统性能
由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。 -
避免对资源的多重占用
例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。 -
优化和共享资源访问
单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点:
-
扩展性差
单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。 -
单例模式对测试是不利的
在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能用mock的方式虚拟一个对象。 -
单例模式与单一职责原则有冲突。
一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
常见实现方式总结
- 饿汉模式
// 1.私有的构造方法 private SingletonE(){ } // 2.创建类的唯一实例,使用private static修饰 private static SingletonE instance = new SingletonE(); // 3.提供一个获取实例的方法,使用public static修饰 public static SingletonE getInstance(){ return instance; }
- 懒汉模式
private SingletonL(){ } private static SingletonL instance; public static SingletonL getInstance(){ if(instance == null) instance = new SingletonL(); return instance; }
两种实现方法的对比
Tables | 饿汉模式 | 懒汉模式 |
---|---|---|
加载类时间 | 较慢 | 较快 |
获取对象速度 | 较快 | 较慢 |
线程是否安全 | 是 | 否 |
*注意:多个线程同时运行这段代码,每次运行结果和单线程运行的结果一致,就是线程安全。反之,则线程不安全。
针对上面懒汉模式线程不安全的缺点,主要由以下几种改进方法
改进一:在getInstance方法上加同步synchronized
public static synchronized SingletonL getSynInstance(){
if(instance == null)
instance = new SingletonL();
return instance;
}
这种方式效率比较低,性能不是太好,不过也可以用,因为是对整个方法加上了线程同步,其实只要在new的时候考虑线程同步就行了,这种方法不推荐使用。
改进二:双重检查锁定
public static SingletonL getDoubleCheckInstance(){
if(instance == null){
synchronized (SingletonL.class){
if(instance == null)
instance = new SingletonL();
}
}
return instance;
}
如果不加synchronized进行控制,如果第一个线程A执行到singleton = new Singleton2(),但还没有获得对象,第二个线程B也在执行,执行到if (singleton == null)判断,那么线程B获得判断条件为真,于是继续运行下去,线程A和B都获得了对象,内存中就出现了两个对象。
声明同步块,形如:synchronized(this){……};
- 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
- 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。
3. 静态内部类
private static class LasyHolder{
private static final SingletonL instance2 = new SingletonL();
}
public static final SingletonL getLasyHolderInstance(){
return LasyHolder.instance2;
}
上面的两种方式下,只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为LasyHolder类没有被主动使用,只有显示通过调用getLasyHolderInstance方法时,才会显示装载LasyHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。
(推荐使用)这种方式既实现了线程安全,又避免了同步带来的性能影响。
网友评论