在我们平时工作 或者 面试的时候,单例模式算是出现最频繁的一种设计模式了,本文整理了单例模式的各种实现
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
自此,上书所写的单例模式基本可以满足所有的情况,谨以此文记之。
如有更好的实现方式,还请不吝赐教!!!