单例模式(Singleton Pattern)

五种方式构建单例模式:懒汉式,饿汉式,双重检测锁(不推荐),静态内部类(推荐),枚举单类(推荐)

单例模式就是保证一个类只有一个实例(对象),并且提供一个访问该实例的全局访问点。

当一个对象的产生需要比较多资源时,如读取配置、产生其他依赖对象时,则可以在应用启动时直接产生一个单例对象,然后驻留内存的方式来解决。

由于单例模式只生成一个实例,减少了系统开销。单例模式可以设置全局访问点,优化共享资源访问,如可以设计一个单例类,负责所有数据表的映射处理。

常见5种单例模式实现方式:

1 饿汉式(线程安全,调用效率高,不能延时加载)

1
2
3
4
5
6
7
8
public class Singleton {
//类初始化时立即加载这个对象,没有延时加载的优势,加载类时,是线程安全的
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}

2 懒汉式(线程安全,调用效率不高,可以延时加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton /*implements Serializable*/ {
//类初始化时,不初始化这个对象,延时加载(懒加载),真正用的时候再创建
private static Singleton instance;
private Singleton() {
//防止反射漏洞可以加上一下语句
/*if (instance != null) {
throw new RuntimeException(); //如果instance不为空,再创建的时候抛出异常
}*/
}
//方法同步调用效率低
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

//防止反序列化生成多个对象
//反序列化时,如果定义了reaResolve()则直接返回此方法指定的对象,而不需要单独创建新对象
/*private Object readResolve() throws ObjectStreamException {
return instance;
}*/
}

使用反射方式直接调用私有构造器:

1
2
3
4
5
Class<Singleton> class = (Class<Singleton>) class.forName("com.xinxing.Singleton");
Constructor<Singleton> constructor = class.getDeclaredConstructor(null);
class.setAccessible(true); //关闭权限访问检测,以访问私有成员
Singleton s1 = constructor.newInstance();
Singleton s2 = constructor.newInstance(); //如果上面没有防漏洞代码,这里s1和s2不相等

通过反序列化的方式构造多个对象:

1
2
3
4
5
6
7
8
9
Singleton s1 = Singleton.getInstance();

FileOutputStream fos = new FileOutputStream("d:/a.txt");
OubjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1); //将s1对象写出到文件
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
Singleton s2 = (Singleton) ois.readObject(); //此时如果没有上面的房序列化代码,这里的s1和s2不同

3 静态内部类式(线程安全,调用效率高,可以延时加载)(推荐使用)

类在被加载时是线程安全的,也是一种懒加载方式,很多框架都用这种框架。

  • 外部类没有static属性,则不会像饿汉模式那样立即加载对象。
  • 只有真正调用getInstanc()时,才会加载静态内部类,加载类时是线程安全的,instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次。
  • 兼备了线程安全、并发高效、延迟加载的优势
1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
//使用静态内部类实现懒加载
private static class SingletonInnerClass {
private static final Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonClass.instance;
}
}

4 枚举单例(线程安全,调用效率高,不能延时加载)(建议使用)

优点:有句JVM从根本上提供保障,避免和反射和反序列化调用私有方法的漏洞,简单高效

缺点:没有延时加载的效果

1
2
3
4
5
6
7
8
9
public enum Singleton {
//定义一个枚举元素,它就代表了Singelton的一个实例
INSTANCE;
//添加自己需要的操作
public void singletonOperation() {}
}

//使用方法
Singleton s = Singleton.INSTANCE; //获得Singleton对象

5 双重检测锁式(不建议使用)

由于懒汉模式是将方法放到同步块里,执行时整个方法都被锁定效率比较低,该模式将同步内容放到if内部,提高了执行效率,不必每次获取对象时都进行同步,只有第一次才同步创建以后就不会同步了。

问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出现问题,不建议使用

五种单例模式在多线程环境下的效率

饿汉式 22ms
懒汉式 636ms
静态内部类式 28ms
枚举式 32ms
双重检查锁式 65ms

典型的应用场景:

  • windows的任务管理器
  • window的回收站
  • 项目中读取配置文件的类
  • 网站计数器
  • 应用程序的日志应用
  • 数据库连接池
  • 操作系统的文件系统
  • Application(Servlet Context)
  • Spring 中每个bean默认就是单例
  • Servlet编程中,每个Servlet也是单例
  • SpringMVC/struts中控制对象