Java程序员从笨鸟到菜鸟(三十一)23种设计模式之单例模式

一、单例模式

背景:

对于某些系统来说,只有一个实例很重要,例如:一个系统中可以存在多个打印任务,但是只能有一个正在执行打印任务;售票窗口,共有100张票,可以多个窗口同时售票,但是不能超售(这里的余票就是单例);在Spring中创建的Bean实例默认都是单例模式存在的

概念:

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例.每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。总之,选择单例模式就是为了避免不一致状态,避免政出多头

写法种类:

  • 懒汉式单例
  • 饿汉式单例
  • 登记式单例

特点:

  1. 单例类只能有一个实例
  2. 单例类必须自己创建自己唯一实例
  3. 单例类必须给所有其他对象提供这一实例

二、 懒汉式、饿汉式、登记式单例实现

懒汉式单例

// 懒汉式单例,在第一次调用时实例化自己
public class Singleton {
    private Singleton() {}
    private static Singleton singleton = null;
    // 静态工厂方法
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

懒汉式单例没有考虑线程安全问题,是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全

方式一:在getInstance方法上加同步

public synchronized static Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

方式二:双重检查锁定

public static Singleton getInstance() {
    if (singleton == null) {
        synchronized (Singleton.class) {
            // 进行两次判断,加入两个线程A,B,线程A先进入实例化对象,如果不加if判断,B线程再次进入时又会实例化一次,这样单例就失效了
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

方式三:静态内部类

private static class LazyHolder {
    private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
    return LazyHolder.INSTANCE;
}

资源加载和性能对比:
方式一:在方法上加同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟大部分情况下是不需要同步的
方式二:在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
方式三:利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种

饿汉式单例

// 饿汉式单例类,在类初始化时,已经自行实例化
public class Singleton1 {
    private Singleton1() {}
    private static final Singleton1 singleton = new Singleton1();
    // 静态工厂方法
    public static Singleton1 getInstance() {
        return singleton;
    }
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的。

登记式单例(可忽略)

public class Singleton2 {
    private static Map<String,Singleton2> map = new HashMap<String,Singleton2>();
    static{
        Singleton2 single = new Singleton2();
        map.put(single.getClass().getName(), single);
    }
    //保护的默认构造子
    protected Singleton2(){}
    //静态工厂方法,返还此类惟一的实例
    public static Singleton2 getInstance(String name) {
        if (name == null) {
            name = Singleton2.class.getName();
            System.out.println("name == null"+"--->name="+name);
        }
        if (map.get(name) == null) {
            try {
                map.put(name, (Singleton2) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
    //一个示意性的商业方法
    public String about() {
        return "Hello, I am RegSingleton.";
    }
    public static void main(String[] args) {
        Singleton2 single2 = Singleton2.getInstance(null);
        System.out.println(single2.about());
    }
}

登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回.。

可以忽略的原因:用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了

扫描二维码关注公众号,回复: 1789908 查看本文章

三、懒汉式和饿汉式的区别

懒汉式:比较懒,只有当调用getInstance的时候,才回去初始化这个实例
饿汉式:类一旦被加载,就把单例初始化完成,保证getInsatnce的时候,单例是已经存在的

线程安全:

懒汉式:线程不安全,但是可以通过上述方式一、二、三来实现线程安全
饿汉式:线程安全,可以直接用于多线程而不会出现问题

资源加载和性能

懒汉式会延迟加载,第一次调用的时候才会实例化对象,第一次调用做初始化,如果要做的工作比较多,性能会有延迟加载现象
饿汉式:在类创建的时候就实例化一个静态对象,不管之后会不会使用单例,都会占一定的内存,但是第一次调用的时候会很快,因为其资源已被初始化完成。

更多设计模式23种设计模式
原文传送门https://blog.csdn.net/jason0539/article/details/44956775

猜你喜欢

转载自blog.csdn.net/u013090299/article/details/80678959