关于单例的那点事

关于单例的那点事

0830

  • 描述:
    • Singleton(单例)是设计模式的一种,为了保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要特点:
    1. 单例类确保自己只有一个实例(构造函数私有:不被外部实例化,也不被继承)。
    2. 单例类必须自己创建自己的实例。
    3. 单例类必须为其他对象提供唯一的实例。
  • 单例模式的应用:
    • 资源管理器,回收站,打印机资源,线程池,缓存,配置信息类,管理类,控制类,门面类,代理类通常被设计为单例类
    • 如果程序有多个类加载器又同时使用单例模式就有可能多个单例并存就要找相应解决方法了
  • 实现方法:
    • 如果应用程序总是创建并使用单例实例或在创建和运行时开销不大。

单例模式的四种写法,占位符模式的写法比较OK,详见如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Elvis
{
private static boolean flag = false;
private Elvis(){
}
private static class SingletonHolder{
private static final Elvis INSTANCE = new Elvis();
}
public static Elvis getInstance()
{
return SingletonHolder.INSTANCE;
}
public void doSomethingElse()
{
}
}

但这都是基于一个条件:确保不会通过反射机制调用私有的构造器。这里举个例子,通过JAVA的反射机制来“攻击”单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ElvisReflectAttack
{
public static void main(String[] args) throws InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
NoSuchMethodException,
SecurityException
{
Class<?> classType = Elvis.class;
Constructor<?> c = classType.getDeclaredConstructor(null);
c.setAccessible(true);
Elvis e1 = (Elvis)c.newInstance();
Elvis e2 = Elvis.getInstance();
System.out.println(e1==e2);
}
}
  • 运行结果:false
  • 可以看到
  • 通过反射获取构造函数,然后调用 setAccessible(true) 就可以调用私有的构造函数,所有e1和e2是两个不同的对象。

如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常

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 {
private static boolean flag = false;
private Singleton() {
synchronized (Singleton.class) {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被狗日了!");
}
}
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public void doSomethingElse() { }
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestSingleton {
public static void main(String[] args) {
try {
Class<Singleton> classType = Singleton.class;
Constructor<Singleton> c = classType.getDeclaredConstructor(null);
c.setAccessible(true);
Singleton e1 = (Singleton) c.newInstance();
Singleton e2 = Singleton.getInstance();
System.out.println(e1 == e2);
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果: 可以看到,成功的阻止了单例模式被破坏

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.ExceptionInInitializerError
at algorithms.Singleton.getInstance(Singleton.java:27)
at algorithms.TestSingleton.main(TestSingleton.java:20)
Caused by: java.lang.RuntimeException: 单例模式被狗日了!
at algorithms.Singleton.<init>(Singleton.java:16)
at algorithms.Singleton.<init>(Singleton.java:3)
at algorithms.Singleton$SingletonHolder.<clinit>(Singleton.java:22)
... 2 more

从JDK1.5开始,实现Singleton还有新的写法,只需编写一个包含单个元素的枚举类型。推荐写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("hello wolrd");
}
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.doSomething();
}
}

EnumSingleton 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestEnumSingleton {
public static void main(String[] args) throws
NoSuchMethodException,
SecurityException,
InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
InvocationTargetException {
Class<EnumSingleton> classType = EnumSingleton.class;
Constructor<EnumSingleton> c = (Constructor<EnumSingleton>) classType.getDeclaredConstructor();
c.setAccessible(true);
c.newInstance();
}
}

执行结果

1
2
3
4
Exception in thread "main" java.lang.NoSuchMethodException: algorithms.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at algorithms.TestEnumSingleton.main(TestEnumSingleton.java:22)

Contents
  1. 1. 关于单例的那点事