最近去平安系面试时,遇到了个人技术领域认定的一大偶像吴大师(Cat作者),他随口问了个单例的问题,要求基于Java技术栈,给出几种单例的方案,并给出单元测试代码,最后要求谈谈单例模式最需要注意的问题时什么?我想想挺简单的,就是一个恶汉,一个懒汉模式,单元测试就一个判断NULL和2个Instance的比较就好。结果被大师劈头盖脸一顿数落,比如我写的懒汉单例(双锁),为什么使用volatile?还有别的更好的方式么?单元测试你不起多个线程,简单的比较有任何意义么?最后被定位为写代码不懂脑筋,仅仅就是照抄别人的成熟方案,缺少对问题关键的把握,无法进行变通。
说实话当时还有些抵触情绪,即使是被自己的偶像批评,不过回家之后好好回顾了相关的问题,发现自己对Java技术栈的理解仍然很浅薄,痛定思痛,决定好好来学习下单例模式和内部类,本文就是基于这两个问题总结,此外,祝大家新的一周工作愉快。
单例模式
在详细介绍单例模式前,首先来谈谈单例模式的目的和问题,尤其是问题部分,我们常常容易忽视(感谢身边同事的提醒)。单例模式的目的非常明确,就是在当前应用中只保存指定对象的一个实例,主要目的是减少资源的消耗,各种提供服务的类会选用该模式。单例模式主要有资源消耗的取舍,线程安全和如何防止反射或序列化破坏单例等三个问题。主要单例的实现模式包括最简单有效的饿汉式、最多变的懒汉式、最优的静态内部类方式、奇特的枚举类方式和综合的登记式模式,本文主要介绍前3种,也是个人认为比较最有价值的3种。
饿汉式
对于一般的业务开发来说饿汉式已经足够,而且Spring框架的单例默认就是饿汉模式,绝大部分的提供各类服务的类都不是很占有内存空间,以此在项目启动时进行预加载对于系统影响不大,即使始终不被使用也没有太大的关系,其代码如下(解决了反射和序列化破坏单例的问题)。
public class HungrySingleton implements Serializable
{
private HungrySingleton(){
if(null != instance){//防止反射破坏单例,场景为二方库调用者强行破坏单例
throw new IllegalOperationException();
}
}
public static final HungrySingleton instance = new HungrySingleton();
public static HungrySingleton getInstance(){
return instance;
}
// 防止反序列化获取多个对象,Java这儿比较奇特,因为在Serializable
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
public class HungrySingletonTest {
@Test
public void testGetInstance() throws Exception {
Class<HungrySingleton> clazz = HungrySingleton.class;
Constructor<?> ctor = clazz.getDeclaredConstructor(null);
ctor.setAccessible(true);
HungrySingleton first = HungrySingleton.getInstance();
HungrySingleton second = (HungrySingleton)ctor.newInstance(null);//破坏单例失败
}
}
懒汉式
懒汉式单例是考点最多的一个,虽然现实中使用次数不是很多,但掌握它有利于了解Java并发编程,常见的实现方式包括加同步锁的懒汉式和防止指令重排优化的懒汉式。
//加同步锁的懒汉式,比较简单,但每次获取实例时都需要获得锁,对性能有一定影响
public static synchronized LazySingleton01 getInstance(){
if(instance == null){
instance = new LazySingleton01();
}
return instance;
}
//防止指令重排优化的懒汉式
//对常见的双锁进行了优化,对instance使用volatile修饰
//再JAVA中,同步块外的判空操作有可能看到已存在,但不完整的实例.
//如果使用不完整的实例则会造成系统崩溃,造成该问题的原因是由于Java内存模型的重排序机制。
public static volatile LazySingleton02 instance = null;
public static LazySingleton02 getInstance(){
if(null == instance){
synchronized (LazySingleton02.class) {
if(null == instance){
instance = new LazySingleton02();
}
}
}
return instance;
}
//单测时一定要注意,需要使用多个线程进行测试,不然就失去了意义
public class LazySingleton02Test {
@Test
public void getInstance() throws InterruptedException, ExecutionException{
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Future<LazySingleton02> futureA = threadPool.submit(
new Callable<LazySingleton02>() {
@Override
public LazySingleton02 call() {
return LazySingleton02.getInstance();
}
}
);
Future<LazySingleton02> futureB = threadPool.submit(
new Callable<LazySingleton02>() {
@Override
public LazySingleton02 call() {
return LazySingleton02.getInstance();
}
}
);
Assert.assertNotNull(futureA.get());
Assert.assertNotNull(futureB.get());
Assert.assertTrue(futureA.get().equals(futureB.get()));
}
}