单例设计模式深度剖析

单例设计模式

单例模式(Singleton Pattern)是设计模式中比较常用的一种,主要思想是保证一个类仅有一个实例,并提供一个访问它的全局访问点。

 单例模式三要点:
  (1)、单例类只能有一个实例

  这是最基本的,真正做到整个系统中唯一并不容易,通常还要考虑反射破坏、序列化/反序列化、对象垃圾回收等问题。

  (2)、单例类必须自己创建自己的唯一实例

  通常给实例构造函数protected或private权限。

  (3)、单例类必须给所有其他对象提供这一实例

  通常定义静态方法getInstance()返回。

饿汉式

public class Hungry {

    private static Hungry hungry = new Hungry();
	private Hungry() {
    }
    public static Hungry instance() {
        return hungry;
    }
}

这种方式不会产生线程安全问题,因为在JVM中,对类的加载和类初始化,由虚拟机保证线程安全。

优点:没有加锁,执行效率会提高。
缺点:可能有时候不需要使用,会浪费内存,如果这个对象很大时,更会影响内存占用。

懒汉式

public class Lazy {

    private static Lazy lazy;

    private Lazy() {
    }

    public static Lazy instance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }

}

可以在instance()时再创建对象,所以称为懒汉式。这种实现最大的问题就是不支持多线程,存在线程安全问题,所以解决此问题产生了双重判断加锁机制。

双重判断加锁机制–懒汉式

public class Lazy {

    private static Lazy lazy;

    private Lazy() {
    }

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

}

这种方式,通过加锁貌似可以解决线程安全问题,并且有了双重判断,第一个if判断加快了执行效率,因为如果判断已经创建了实例,就不需要再进行加锁,但是,仔细思考,发现还是存在问题,原因就在于,对象的实例化过程不是一个原子操作,对于lazy = new Lazy();这步操作,在虚拟机里面是分为两步执行的,分别是实例化对象、给lazy变量赋地址值,这两步操作由于不是原子操作,并且java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制,所以可能存在的情况是两个线程,A线程先执行到这一步,先完成了对象的实例化,还没有完成给变量赋值的时候,此时A线程退出了,然后B线程拿到锁后,因为已经实例化了,但是又未给变量赋值,所以继续进入if块进行初始化赋值,最后退出,这时就会造成堆内存中出现两个Lazy的实例。
这个问题的产生根本就是数据的变化不可见,所以可以采用volatile避免。

public class Lazy {

    private static volatile Lazy lazy;

    private Lazy() {
    }

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

}

但是,这样的代码过于臃肿,不便于维护,可读性也差,所以由此产生了另一种更好的方式。

懒加载

public class LazyLoad {

    private static class LazyHolder {
        private final static LazyLoad lazyLoad = new LazyLoad();
    }

    private LazyLoad() {
    }

    public static LazyLoad instance() {
        return LazyHolder.lazyLoad;
    }
}

这种方式最为优雅,并且可以避免线程安全问题,定义私有内部类,在类加载的时候不会加载私有内部类,只有在调用instance方法的时候才会初始化。这种延迟加载模式,也叫类初始化模式和延迟占位模式。
这种方式在其他地方也应用的非常广,比如:
当一个对象内部的属性是一个占用内存很大,但又不经常使用的对象,如果不使用的时候直接创建会浪费内存空间,所以这时候可以采用这种方式:

public class LazyLoadExmp {

    private String name;

    //这是一个非常大的数组,不经常使用
    private Integer[] integers;


    //这种就可以采用延迟加载模式
    private static class LazyHolder {
        private final static Integer[] integers = new Integer[100000000];
    }

    public Integer[] getIntegers() {
        return LazyHolder.integers;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

猜你喜欢

转载自blog.csdn.net/qq_28822933/article/details/83243553