单例设计模式

单例模式的定义与特点


单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式有 3 个特点:

单例类只有一个实例对象;

该单例对象必须由单例类自行创建;

单例类对外提供一个访问该单例的全局访问点。

单例模式的优点和缺点


单例模式的优点:

单例模式可以保证内存里只有一个实例,减少了内存的开销。

可以避免对资源的多重占用。

单例模式设置全局访问点,可以优化和共享资源的访问。

单例模式的缺点:

单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。

在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。

单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

 

饿汉模式

public final class HungrySingle {

    private static HungrySingle instance=new HungrySingle();

    private HungrySingle(){}

    public static HungrySingle getInstance() {
        return instance;
    }
}

 将类的构造函数定义模式为private,让其他类无法实例化此类,然后提供一个静态方法给调用者。好处是类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了线程同步的问题,坏处是在没用的情况下也会被创建,浪费了内存。如果在初始化阶段就能用到,占用内存也比较小,适合用这种。

懒汉模式

public final class LazySingle {

    private static LazySingle instance=null;

    private LazySingle(){}

    public static LazySingle getInstance(){
        if (null==instance){
            instance=new LazySingle();
        }
        return instance;
    }
}

好处是只有在需要时才会被创建,如果单例已经创建,再次调用也不会创建新的对象。适用于使用次数较少,并且消耗的单例资源较多的情况,就可以按需创建。但这里的懒汉模式没考虑线程的安全问题,可能会存在多个线程并发调用方法的问题,所以下面有解决办法:

public final class LazySingleSecondChoice {

    private static LazySingleSecondChoice instance=null;

    private LazySingleSecondChoice(){}

    public static synchronized LazySingleSecondChoice getInstance() {
        if (null==instance){
            instance=new LazySingleSecondChoice();
        }
        return instance;
    }
}

但同步锁也会有另一个缺点,就是增加锁竞争,带来性能开销,导致性能下降,会降低单例模式的性能。所以为了解决找个问题,我们可以将同步锁放在if里面,因为除了第一次创建的时候为null,其他时候基本都不会为null的情况。只有当if条件判断为null时,才会创建实例,所以可以将同步锁放在里面,减少竞争

public final class LazySingleThirdChoice {

    private static LazySingleThirdChoice instance= null;

    private LazySingleThirdChoice(){}

    public static LazySingleThirdChoice getInstance() {
        if (null==instance){
            synchronized (LazySingleThirdChoice.class){
                instance=new LazySingleThirdChoice();
            }
        }
        return instance;
    }
}

 

但是现在依然会创建多个实例。这是因为当多个线程进入到 if 判断条件里,虽然有同步锁,但是进入到判断条件里面的线程依然会依次获取到锁创建对象,然后再释放同步锁。所以我们还需要在同步锁里面再加一个判断条件 ,双重校验锁:

public class DoubleSynchronizedLazySingle {

    private static DoubleSynchronizedLazySingle instance=null;

    private DoubleSynchronizedLazySingle(){}

    public static DoubleSynchronizedLazySingle getInstance() {
        if (null==instance){
            synchronized (DoubleSynchronizedLazySingle.class){
                if (null==instance) // 判断两次
                instance=new DoubleSynchronizedLazySingle();
            }
        }
        return instance;
    }

}

 静态内部类

利用了类加载机制来保证只创建一个实例,因此不会存在多线程的问题。又因为使用了内部类的方法,所以不会在JVM的时候就进行创建,只有当调用内部类方法时,才会创建。推荐这种方法,同时保证了延迟加载和线程安全

public final class StaticSingle {

    public List list=null;

    private StaticSingle(){}

    public static class InnerSingle{
        private static StaticSingle instance=new StaticSingle();
    }

    public static StaticSingle getInstance(){
        return InnerSingle.instance;
    }

}

 

 希望以上能帮助到你在什么时候选择什么方式,如果在项目启动后就一定会需要,可以使用饿汉式,如果是工具类,希望在需要的时刻在进行创建,建议懒汉式,避免占用内存资源

你可能感兴趣的:(设计模式)