Singleton Pattern单例模式总结

在我们平时工作 或者 面试的时候,单例模式算是出现最频繁的一种设计模式了,本文整理了单例模式的各种实现

1、饿汉模式【简而言之,就是不管你用不用,我先创建出来】

public class HungrySingleton {

    private static final HungrySingleton hungry = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungry;
    }
}

还有另一种写法,利用静态代码块的机制来实现

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungry;

    static {
        hungry = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getInstance() {
        return hungry;
    }

}

优点:没有锁,执行效率比较高,用户体验比懒汉模式要好

缺点:浪费内存【用不用都会先创建】

 

2、懒汉模式【用的时候再创建实例】

public class LazySimpleSingleton {

    public LazySimpleSingleton() {}

    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

上面的懒汉模式是我们最常见的,但再多线程环境下可能创建多个实例,有兴趣的可以自己试一下。

解决多线程环境下的安全隐患,最先想到的就是加锁,如下所示:

public class LazySimpleSingleton {

    public LazySimpleSingleton() {}

    private static LazySimpleSingleton lazy = null;

    public static synchronized LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

这种方式虽然解决了多线程的安全问题,但是当线程比较多的时候,大批线程会阻塞,CUP的压力也会上升。

那么我们怎么能解决这种情况呢,可以使用双重检查锁的方式来解决,如下所示:

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton() {}

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

以上情况会有很大的改观,但用到锁,终归还是有一定的影响。有什么更好的方法呢?可以考虑采用静态内部类的方式来解决。

 

3、静态内部类

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {}

    // 每一个关键字都不是多余的,static 是为了使单例的空间共享,保证这个方法不会被重写、重载
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回结果之前一定会加载内部类
        return lazyHolder.LAZY;
    }

    // 静态内部类 默认不加载
    private static class lazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

这种方法可以很好的兼顾饿汉单例模式的内存浪费问题和 synchronized 的性能问题,完美的屏蔽了这两个缺点。

 

4、反射破坏单例

上面的形式在大部分情况下都是没有问题的,但是如果使用反射来创建对象的,我们发现仍然是可以创建两个实例

public class Test {

    public static void main(String[] args) {

        // 利用反射则会破坏内部类单例模式
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o1 = c.newInstance();
            Object o2 = c.newInstance();
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

返回结果

false

显然,创建了两个不同的实例,我们可以在构造函数中加一些限制,一旦重复创建就抛出异常或者指定的信息。如下所示:

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
        // 此构造函数中做限制,保证反射不会被反射破坏【使用序列化会创建多个实例】
        if (lazyHolder.LAZY != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    // 每一个关键字都不是多余的,static 是为了使单例的空间共享,保证这个方法不会被重写、重载
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回结果之前一定会加载内部类
        return lazyHolder.LAZY;
    }

    // 静态内部类 默认不加载
    private static class lazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

同样使用上方的多线程进行测试,运行代码,控制台结果如下:

java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.xiang.designPattern.singleton.Test.main(Test.java:26)
Caused by: java.lang.RuntimeException: 不允许创建多个实例
	at com.xiang.designPattern.singleton.LazyInnerClassSingleton.<init>(LazyInnerClassSingleton.java:14)
	... 5 more

自此,上书所写的单例模式基本可以满足所有的情况,谨以此文记之。

如有更好的实现方式,还请不吝赐教!!!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章