单例模式
标签 : Java与设计模式
经典的《设计模式:可复用面向对象软件的基础》一书归纳出23种设计模式, 23种设计模式又可划分为3类: 创建型模式, 结构型模式, 行为型模式.
创建型模式
社会分工越来越细, 在软件设计方面自然也是如此,对象的创建和使用分开也就成了必然趋势: 如果对象创建会消耗很多系统资源, 那么单独对对象的创建进行研究, 从而高效地创建对象就是 创建型模式 要探讨的问题. 有6个具体的创建型模式可供研究,它们分别是: 单例模式 、工厂模式(简单工厂/工厂方法/抽象工厂) 、建造者模式 、原型模式.
单例模式
保证一个类只有一个实例, 并提供一个访问他的全局访问点.
- 场景:
- Windows任务管理器;
文件系统
: 一个操作系统只能有一个文件系统;- 数据库连接池;
- Spring: 一个
Component
就只有一个实例; - JavaWeb: 一个
Servlet
只有一个实例;
实现
常见的单例模式实现方式有五种: 饿汉式, 懒汉式, 双重检测锁, 静态内部类, enum枚举.
实现要点:
- 隐藏构造器
- static Singleton实例
- 暴露实例获取方法
追求目标
- 线程安全
- 调用效率高
- 延迟加载
1. 饿汉式
/**
* 饿汉式
* 问题: 如果只是加载本类, 而没有调用getInstance方法, 会造成资源浪费
* Created by jifang on 15/12/4.
*/
public class HungerSingleton {
/**
* 类初始化时理解初始化该实例
* 类加载时, 天然的线程安全时刻
*/
private static final HungerSingleton instance = new HungerSingleton();
private HungerSingleton() {
}
/**
* 方法没有同步(synchronized), 调用效率高
*/
public static HungerSingleton getInstance() {
return instance;
}
@Override
public String toString() {
return "HungerSingleton{}";
}
}
2. 懒汉式
/**
* 懒汉式
* 问题: 每次调用getInstance都要同步(synchronized), 效率降低
* Created by jifang on 15/12/4.
*/
public class LazySingleton {
/**
* 类加载时并没初始化, 延迟加载
*/
private static LazySingleton instance;
private LazySingleton() {
}
/**
* 注意synchronized, 线程安全
*/
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
@Override
public String toString() {
return "LazySingleton{}";
}
}
3. 双重检测锁
由于同步(synchronized
)只在第一次实例化Instance
时才需要,也就是单例类实例创建时, 因此我们使用双重检测锁(double checked locking pattern)实现:
/**
* 双重锁定实现
* 问题: 适用于JDK1.5之后的版本
* Created by jifang on 15/12/4.
*/
public class DoubleCheckSingleton {
/**
* 需要使用volatile
* 保证所有的写(write)都将先行发生于读(read)
*/
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (instance == null) { //Single Checked
synchronized (DoubleCheckSingleton.class) {
if (instance == null) { // Double Checked
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
@Override
public String toString() {
return "DoubleCheckSingleton{}";
}
}
注: 有文章中指出 由于JVM底层内部模型原因, 双重锁定偶尔会出问题, 不建议使用, 但自1.5开始, 该问题已经被解决, 因此可放心使用, 详见如何在Java中使用双重检查锁实现单例.
4. 静态内部类
/**
* 静态内部类实现Singleton
* Created by jifang on 15/12/4.
*/
public class StaticInnerSingleton {
/**
* 外部类没有static属性, 因此加载本类时不会立即初始化对象
*/
private static class InnerClassInstance {
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
}
private StaticInnerSingleton() {
}
/**
* 只有真正调用getInstance方法时, 才会加载静态内部类(延迟加载), 而且加载类是天然的线程安全的(线程安全), 没有synchronized(调用效率高)
*
* @return
*/
public static StaticInnerSingleton getInstance() {
return InnerClassInstance.instance;
}
@Override
public String toString() {
return "StaticInnerSingleton{}";
}
}
5. 枚举
/**
* 枚举实现单例
* 基于JVM底层实现, Enum天然的单例以及线程安全
* Created by jifang on 15/12/5.
*/
public enum EnumSingleton {
/**
* 构造方法默认为private
*/
INSTANCE;
/**
* 可以添加其他操作
* other operation
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "EnumSingleton{" +
"name='" + name + '\'' +
'}';
}
}
实现对比
方式 | 优点 | 缺点 |
---|---|---|
饿汉式 | 线程安全, 调用效率高 | 不能延迟加载 |
懒汉式 | 线程安全, 可以延迟加载 | 调用效率不高 |
双重检测锁 | 线程安全, 调用效率高, 可以延迟加载 | - |
静态内部类 | 线程安全, 调用效率高, 可以延迟加载 | - |
枚举 | 线程安全, 调用效率高 | 不能延迟加载 |
破解与防御
反射破解单例
可以利用Java的反射机制破解单例模式(Enum
无法破解, 由于基于JVM底层实现),下面仅破解双重检测锁, 其他类同不再赘述:
/**
* 单例破解
* Created by jifang on 15/12/4.
*/
public class TestCase {
@Test
public void testBreakDoubleCheck() {
try {
Class<DoubleCheckSingleton> clazz = (Class<DoubleCheckSingleton>) Class.forName("com.feiqing.singleton.DoubleCheckSingleton");
Constructor<DoubleCheckSingleton> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
DoubleCheckSingleton instance1 = constructor.newInstance();
DoubleCheckSingleton instance2 = constructor.newInstance();
System.out.println("singleton? " + (instance1 == instance2));
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
序列化破解单例
- 对懒汉式破解, 需要首先对
LazySingleton
进行改造,支持序列化:
public class LazySingleton implements Serializable{
private static final long serialVersionUID = 8511876423469188139L;
/**
* 类加载时并没初始化, 延迟加载
*/
private static LazySingleton instance;
private LazySingleton() {
if (instance != null){
throw new RuntimeException();
}
}
/**
* 注意synchronized, 线程安全
*/
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
@Override
public String toString() {
return "LazySingleton{}";
}
}
- 破解:
public class TestCase {
private static final String SYSTEM_FILE = "/tmp/save.txt";
@Test
public void testBreakLazy() {
LazySingleton instance1 = LazySingleton.getInstance();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SYSTEM_FILE));
oos.writeObject(instance1);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SYSTEM_FILE));
LazySingleton instance2 = (LazySingleton) ois.readObject();
System.out.println("singleton? " + (instance1 == instance2));
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
序列化防御
- 在
LazySingleton
添加readResolve()
方法:
/**
* 反序列化时, 如果定义了readResolve方法, 则直接返回此方法制定的对象.
*
* @return
*/
private Object readResolve() {
return instance;
}
详细可参考: 深入理解Java对象序列化.
性能测试
/**
* 单例性能测试
* Created by jifang on 15/12/4.
*/
public class TestCase {
private static final String SYSTEM_FILE = "/tmp/save.txt";
private static final int THREAD_COUNT = 10;
private static final int CIRCLE_COUNT = 100000;
@Test
public void testSingletonPerformance() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
FileWriter writer = new FileWriter(new File(SYSTEM_FILE), true);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; ++i) {
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i < CIRCLE_COUNT; ++i) {
Object instance = HungerSingleton.getInstance();
}
latch.countDown();
}
}
).start();
}
latch.await();
long end = System.currentTimeMillis();
writer.append("HungerSingleton 共耗时: " + (end - start) + " 毫秒\n");
writer.close();
}
}
- 结果
* | 单例实现 | 耗时 |
---|---|---|
1 | HungerSingleton |
30 毫秒 |
2 | LazySingleton |
48 毫秒 |
3 | DoubleCheckSingleton |
25 毫秒 |
4 | StaticInnerSingleton |
16 毫秒 |
5 | EnumSingleton |
6 毫秒 |
Enum
毫无疑问的成为了实现单例的王者, <Effective Java>中也推荐使用, 因此Enum成为Java中实现单例的最好方式, 但是Enum也有其自身的限制, 因此在使用时还需要做一番权衡.
小结
由于单例模式只生成一个实例, 减少了系统性能开销, 因此 当一个对象的产生需要比较多的资源时(如读取配置/产生其他依赖对象), 则可以通过只产生一个单例对象, 然后永久驻留内存的方式来提高系统整体性能.