单例模式

单例模式的定义:

         单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类

只有一个实例,而且自行实例化并向整个系统提供这个实例。)

单例模式的通用类图如图

 

Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton))。

单例模式的通用源代码实现

/**
 * 单例模式的通用实现
 */
public class Singleton {
    // 自行实例化
    private static final Singleton singleton = new Singleton();

    // 私有化构造函数
    private Singleton(){

    }

    /**
     * 提供外部获取实例的方法
     */
    public static Singleton getInstance(){
        return singleton;
    }

    /**
     * 也可以提供其它方法: 但是该类中的其它方法也尽量保持位static
     */
    public static void otherMethod(){
        
    }
}

单例模式的优点: 

1、单例模式在内存中只有一个实例

由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

2、有效的减少系统的开销

由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制)。

3、有效的避免资源多重占用

单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

4、优化共享资源访问

单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

 

单例模式的缺点

1、对单例模式的扩展困难

单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。

2、单例模式不利于测试工作的展开

单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

3、冲突单一职责原则

单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

 

单例模式的使用场景

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:

  1. 要求生成唯一序列号的环境;
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
  3. 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。

 

单例模式的注意事项

在高并发的场景下单例模式的线程同步问题。上面的代码不会出现内存中出现多个实例的情况。但是以下模式代码会出现该情况。

/**
 * 线程不安全的单例模式实现
 */
public class SingletonWithThreadNotSafe {
    private static SingletonWithThreadNotSafe singleton = null;
    /**
     * 私有化构造函数
     */
    private SingletonWithThreadNotSafe(){
    }

    // 实例获取
    public static SingletonWithThreadNotSafe getInstance(){
        if (singleton == null){
            singleton = new SingletonWithThreadNotSafe();
        }
        return singleton;
    }
}

该单例模式在低并发的情况下尚不会出现问题,若系统压力增大,并发量增加时则可能在内存中出现多个实例,破坏了最初的预期。

线程安全问题分析:

为什么会出现这种情况呢?如一个线程A执行到singleton=new Singleton),但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到(singleton==null)判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现两个对象!

线程安全问题解决

解决线程不安全的方法很有多可以在getSingleton方法前加synchronized关键字也可以|getSingleton方法内增加synchronized来实现但都不是最优秀的单例模式建议以上所示的方式实现单例模式该模式常被人们称为饿汉模式,如果在第二种方法上添加了synchronized关键字的话就变成了懒汉模式了。

其次,需要考虑对象的复制情况。在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法则可以直接通过对象复制方式创建一个新对象,对象复制是不用调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。在一般情况下,类复制的情况不需要考虑,很少会出现一个单例类会主动要求被复制的情况,解决该问题的最好方法就是单例类不要实现Cloneable接口。

 

单例模式多种实现方式Java版本总结

懒汉模式实现解析

懒汉模式在第一次调用的时候实例化。

package designPatterns.singleton;

/**
 * 单例模式懒汉模式实现总结: 单线程环境(懒汉模式的多种实现解析)
 */
public class Singleton_ONE {
    // 声明实例
    private static Singleton_ONE singleton_noe = null;

    // 私有化构造方法
    private Singleton_ONE(){

    }

    /**
     * 实例获取:适用于单线程环境。不推荐使用
     * 此方式在单线程的时候工作正常,但在多线程的情况下就有问题了。如果两个线程同时运行到
     * 判断instance是否为null的if语句,并且instance的确没有被创建时,那么两个线程都会创建
     * 一个实例,此时类型Singleton1就不再满足单例模式的要求了。
     * @return
     */
    public static Singleton_ONE genInstance(){
        if(singleton_noe == null){
            singleton_noe = new Singleton_ONE();
        }
        return singleton_noe;
    }

    /**
     * 实例获取: 适用于高并发多线程环境,由于使用了synchronized的锁机制,效率不高。(不推荐)
     * 为了保证在多线程环境下我们还是只能得到该类的一个实例,只需要在getInstanceThreadSafe()方法加上同步关键字sychronized,
     * 就可以了。getInstanceThreadSafe()方法时都被synchronized关键字锁住了,会引起线程阻塞,影响程序的性能。
     * @return
     */
    public static synchronized Singleton_ONE getInstanceThreadSafe(){
        if(singleton_noe == null){
            singleton_noe = new Singleton_ONE();
        }
        return singleton_noe;
    }

    /**
     * 双重加锁机制(推荐使用)
     * 为了在多线程环境下,不影响程序的性能,不让线程每次调用getInstanceC()方法时都加锁,
     * 而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在。
     * @return
     */
    public static Singleton_ONE genInstanceC(){
        // 优先判断实例是否存在,若不存在再进行加锁处理
        if(singleton_noe == null){
            synchronized (Singleton_ONE.class){
                if(singleton_noe == null){
                    singleton_noe = new Singleton_ONE();
                }
            }
        }
        return singleton_noe;
    }

}

 

静态内部类方式

加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。 由于在调用 StaticSingleton.getInstance() 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的;由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。

总结:

优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。

劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久代的对象。

package designPatterns.singleton;

/**
 * 单例模式的通用实现
 */
public class Singleton {
    // 自行实例化
    private static final Singleton singleton = new Singleton();

    // 私有化构造函数
    private Singleton(){

    }

    /**
     * 提供外部获取实例的方法
     */
    public static Singleton getInstance(){
        return singleton;
    }

    /**
     * 也可以提供其它方法: 但是该类中的其它方法也尽量保持位static
     */
    public static void otherMethod(){

    }
}

 

枚举方式(推荐使用)

创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)。

/**
 * 枚举方式
 */
public class Singleton {
    public static void main(String[] args) {
        Single single = Single.SINGLE;
        single.print();
    }

    enum Single {
        SINGLE;

        private Single() {
        }

        public void print() {
            System.out.println("hello world");
        }
    }
}

就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举方式来实现单例。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值