确保一个类在任何时候都绝对只有一个实例,并提供一个全局访问点。分为饿汉模式和懒汉模式。
饿汉单例模式
饿汉模式适用于单例对象较少的情况。
写法一:直接在使用 static 关键字创建对象
1 2 3 4 5 6 7 8 9 10
| public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() { }
public static HungrySingleton getInstance() { return hungrySingleton; } }
|
写法二:使用 static{} 的方式创建对象
1 2 3 4 5 6 7 8 9 10
| public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton;
static { hungrySingleton = new HungryStaticSingleton(); } public static HungryStaticSingleton getInstance() { return hungrySingleton; } }
|
懒汉单例模式
顾名思义,被外部调用的时候内部类才会加载。
简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class LazySimpleSingleton { public LazySimpleSingleton() { }
private static LazySimpleSingleton singleton;
public static LazySimpleSingleton getInstance() { if (singleton == null) { singleton = new LazySimpleSingleton(); } return singleton; } }
|
上面这种方式存在安全隐患,下面看一个案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ExecutorThread implements Runnable { @Override public void run() { LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " -> " + singleton); }
public static void main(String[] args) { new Thread(new ExecutorThread()).start(); new Thread(new ExecutorThread()).start(); System.out.println("=============END=============="); } }
|
执行结果:
1 2 3
| =============END============== Thread-0 -> com.fun.DesignPatterns.singleton.LazySimpleSingleton@2ca9c4e8 Thread-1 -> com.fun.DesignPatterns.singleton.LazySimpleSingleton@4e3058f8
|
结果有的时候一致有的时候不一致,发现在线程的环境下,这个对象被实例化了两次。给SimpleSigleton 创建对象的方法加上 synchronized 修饰符:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class LazySimpleSingleton { public LazySimpleSingleton() { }
private static LazySimpleSingleton singleton;
public synchronized static LazySimpleSingleton getInstance() { if (singleton == null) { singleton = new LazySimpleSingleton(); } return singleton; } }
|
现在线程的安全问题解决了,但是如果在线程数量比较多的情况下加锁,会让CPU分配压力增大,导致大量线程阻塞,从而降低程序性能,继续改进。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton singleton = null;
public LazyDoubleCheckSingleton() { }
public static LazyDoubleCheckSingleton getInstance() { if (singleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (singleton == null) { singleton = new LazyDoubleCheckSingleton(); } } } return singleton; } }
|
上面使用双重检查锁(DCL-Double Check Lock)的单例模式既能兼顾线程又能提升程序性能。但终归是需要上锁,对性能还是有一定的影响,还有下面这种更好的的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class LazyInnerClassSingleton { public LazyInnerClassSingleton() { }
private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); }
public final static LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } }
|
这种方法的优点就很明显了:不上锁、提升性能、线程安全、懒加载。此外,面试经常被问的问题还有以下几点。
单例模式被破坏的问题:反射破坏、序列化破坏、克隆破坏(下一节,请见原型模式)。解决方法下面第三点:
注册式单例模式
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
枚举式单例模式
枚举式单例可避免序列化破坏和反射破坏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance(){ return INSTANCE; } }
|
容器缓存单例模式
容器式单例适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc=new ConcurrentHashMap<String,Object>(); public static Object getBean(String className){ synchronized (ioc){ if(!ioc.containsKey(className)){ Object obj=null; try { obj=Class.forName(className); ioc.put(className,obj); } catch (ClassNotFoundException e) { e.printStackTrace(); } return obj; }else{ return ioc.get(className); } } } }
|
到此注册式单例模式就完了,下面在补充一种ThreadLocal实现单例模式的案例:
ThreadLocal 单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ThreadLocalSingleton { public ThreadLocalSingleton() {}
private static final ThreadLocal<ThreadLocalSingleton> singleton = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } };
public static ThreadLocalSingleton getInstance() { return singleton.get(); } }
|