03-单例模式

参考资料1:http://c.biancheng.net/design_pattern/

参考资料2:https://refactoringguru.cn/design-patterns/catalog

01. 单例设计模式

  • 作用
    • 在java virtual machine(JVM)中只创建一个实例
  • 分类
    • 饿汉单例设计模式
    • 懒汉单例设计模式
    • 同步懒汉单例设计模式
    • 双重锁校验单例设计模式
    • 静态内部类单例设计模式
    • 枚举单例设计模式 - 推荐
    • ThreadLocal线程单例

1.1 饿汉式-推荐

  • 步骤

    • 私有化构造器
    • 在本类中声明一个本类对象,并使用private static修饰
    • 给本类对象提供一个get方法
  • 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestSingleton {
public static void main(String[] args) {
Singleton1 s1 = Singleton1.getInstance();
Singleton1 s11 = Singleton1.getInstance();
System.out.println(s1); // Singleton1@7852e922
System.out.println(s11); // Singleton1@7852e922
}
}

class Singleton1 {
// 私有静态常量引用,类加载执行就1份、不可调用、不可修改(始终单例)
private static final Singleton1 instance = new Singleton1();
// 私有构造,不可直接new对象
private Singleton1() {}
// 静态方法,类名直接调用,返回私有静态常量引用
public static Singleton1 getInstance() {
return instance;
}
}
  • 特点(天生线程安全(无锁)、类加载时创建(不用也会被创建,占用资源))

    • 效率高
    • 线程安全
    • 不支持懒加载(lazy load)

1.2 懒汉式

  • 步骤
    • 私有化构造器
    • 在本类中创建一个本类引用,并使用private static修饰
    • 给本类对象提供一个get方法
  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestSingleton {
public static void main(String[] args) {
Singleton2 s2 = Singleton2.getInstance();
Singleton2 s22 = Singleton2.getInstance();
System.out.println(s2); // Singleton2@4e25154f
System.out.println(s22); // Singleton2@4e25154f
}
}

class Singleton2 {
// 私有静态引用,类加载执行就1份、不可调用
private static Singleton2 instance = null;
// 私有构造,不可直接new对象
private Singleton2() {}
// 同步锁、静态方法获取类的对象(引用为空则new,不为空则返回自身,始终单例)
public synchronized static Singleton2 getInstance() {
return instance == null ? instance = new Singleton2() : instance;
}
}
  • 特点(天生线程不安全(需同步锁、效率低)、使用时才创建)

    • 效率高
    • 线程不安全
    • 支持懒加载

饿汉和懒汉的区别:

  • 饿汉
    • 效率高
    • 线程安全
    • 不支持懒加载(lazy load)
  • 懒汉
    • 效率高
    • 线程不安全
    • 支持懒加载
  • 懒加载
    • lazy load ,当一个资源被使用时才加载,不使用时不加载。
  • 区别
    • 饿汉是以空间换时间(执行效率相对高些),懒汉是以时间换空间(执行效率相对低些)
    • 饿汉是线程安全的,懒汉是线程不安全的。(有所谓!!!)
    • 饿汉不支持懒加载,懒汉支持懒加载

1.3 同步懒汉式

  • 作用

    • 解决懒汉单例设计模式的线程不安全问题
  • 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MySingleClass03 {
private MySingleClass03(){}
private static MySingleClass03 instance = null;
public static MySingleClass03 getInstance() {
// 同步代码块中 new 单例对象
synchronized (MySingleClass03.class) {
if (instance == null) {
instance = new MySingleClass03();
}
}
return instance;
}
}
  • 特点(天生线程安全(无锁),使用时才创建(静态内部类))

    • 效率非常低
    • 线程安全
    • 支持懒加载

1.4 双重锁校验式-面试常见

  • 作用

    • 解决同步懒汉单例设计模式的效率低问题
  • 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MySingleClass04 {
private MySingleClass04(){}
private volatile static MySingleClass04 instance = null;
public static MySingleClass04 getInstance() {
if (null == instance) {
synchronized (MySingleClass04.class) {
if (instance == null) {
instance = new MySingleClass04();
}
}
}
return instance;
}
}
  • 特点

    • 效率高
    • 线程安全
    • 支持懒加载

1.5 静态内部类式

  • 步骤
    • 私有化构造器
    • 声明一个静态内部类
      • 初始化单例对象,并使用private final static修饰
    • 提供一个get方法
  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestSingleton {
public static void main(String[] args) {
Singleton3 s3 = Singleton3.getInstance();
Singleton3 s33 = Singleton3.getInstance();
System.out.println(s3); // Singleton3@70dea4e
System.out.println(s33); // Singleton3@70dea4e
}
}

//这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
class Singleton3 {
//私有构造,不可直接new对象
private Singleton3() {}
//静态方法,获取内部类中创建的外部类对象的常量引用
public static final Singleton3 getInstance() {
//在返回结果前一定会先加载内部类
return Holder.instancce;
}
//静态内部类,不依赖外部类,使用时才需要创建,可独立创建
//内部类中静态常量引用,一旦创建内部类,属性则不可修改(始终单例)
private static class Holder {
private static final Singleton3 instancce = new Singleton3();
}
}
  • 特点
    • 效率高
    • 线程安全
    • 支持懒加载

1.6 枚举式 - 推荐

  • 枚举

    • 数学计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。
    • 就是一个天然的多例设计模式
  • 枚举实现

1
2
3
public enum SevenColor {
RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE
}
  • 枚举单例设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum MySingleClass04 {
INSTANCE;
private Object data;

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

public static MySingleClass04 getInstance() {
return INSTANCE;
}
}

image-20230716000314400

  • 反射攻击枚举单例设计模式
  • 反射攻击无参构造器
1
2
3
4
5
MySingleClass04 instance01 = MySingleClass04.INSTANCE;
Constructor<MySingleClass04> c = MySingleClass04.class.getDeclaredConstructor();
c.setAccessible(true);
MySingleClass04 instance02 = c.newInstance();
System.out.println(instance01 == instance02);
  • image-20200526114847725
  • 通过查看 Enum 类的源码发现,枚举类是没有无参构造,再通过反射攻击有参构造器
  • 反射攻击有参构造器
1
2
3
4
5
MySingleClass04 instance01 = MySingleClass04.INSTANCE;
Constructor<MySingleClass04> c = MySingleClass04.class.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
MySingleClass04 instance02 = c.newInstance("hello", 110);
System.out.println(instance01 == instance02);
  • image-20200526115044026

  • image-20200526115112551

  • 结论

    • 枚举单例设计模式可以避免反射攻击
  • 序列化攻击枚举单例设计模式

1
2
3
4
5
6
7
8
9
MySingleClass04 instance01 = MySingleClass04.INSTANCE;

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.obj"));
oos.writeObject(instance01);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.obj"));
Object instance02 = ois.readObject();
ois.close();
System.out.println(instance01 == instance02);
  • 以上代码的结果为true,说明枚举单例设计模式可以避免序列化攻击

  • 总结

    • 枚举单例设计模式既可以避免反射攻击,也可以避免序列化攻击
  • 特点

    • 效率高
    • 线程安全
    • 不支持懒加载
    • 避免反射攻击
    • 避免序列化攻击

1.7 ThreadLocal线程单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};

private ThreadLocalSingleton() {}

public static ThreadLocalSingleton getInstance() {
return threadLocalInstance.get();
}
}
1
2
3
4
5
6
7
public class ExectorThread implements Runnable {
@Override
public void run() {
ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestSingleton {
public static void main(String[] args) {
//主线程:在主线程 main 中无论调用多少次,获取到的实例都是同一个
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
//子线程:都在两个子线 程中分别获取到了不同的实例。
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}

image-20220314225910821

上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的。

02. 反射攻击单例设计模式

  • 所学的单例设计模式

    • 饿汉单例设计模式(推荐用)
      • 效率高
      • 线程安全
      • 不支持懒加载
    • 懒汉单例设计模式
      • 效率高
      • 线程不安全
      • 支持懒加载
    • 同步懒汉单例设计模式
      • 效率非常低
      • 线程安全
      • 支持懒加载
    • 双重锁单例设计模式(推荐用)
      • 效率高
      • 线程安全
      • 支持懒加载
    • 静态内部类单例设计模式(推荐用)
      • 效率高
      • 线程安全
      • 支持懒加载
  • 存在的问题

  • 无法避免反射攻击

  • 反射攻击单例设计模式

    • 饿汉单例设计模式
      • MySingleClass01
    • 双重锁单例设计模式
      • MySingleClass02
    • 静态内部类单例设计模式
      • MySingleClass03
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
        //饿汉单例设计模式
MySingleClass01 instance01 = MySingleClass01.getInstance();
// MySingleClass01 instance02 = MySingleClass01.getInstance();
Constructor<MySingleClass01> c1 = MySingleClass01.class.getDeclaredConstructor();
c1.setAccessible(true);
MySingleClass01 instance02 = c1.newInstance();

System.out.println(instance01 == instance02);

System.out.println("------------------------------");
//双重锁单例设计模式
MySingleClass02 instance03 = MySingleClass02.getInstance();
//// MySingleClass02 instance04 = MySingleClass02.getInstance();
Constructor<MySingleClass02> c2 = MySingleClass02.class.getDeclaredConstructor();
c2.setAccessible(true);
MySingleClass02 instance04 = c2.newInstance();
System.out.println(instance03 == instance04);

System.out.println("------------------------------");
//静态内部类单例设计模式
MySingleClass03 instance05 = MySingleClass03.getInstance();
// MySingleClass03 instance06 = MySingleClass03.getInstance();

Constructor<MySingleClass03> c3 = MySingleClass03.class.getDeclaredConstructor();
c3.setAccessible(true);
MySingleClass03 instance06 = c3.newInstance();
System.out.println(instance05 == instance06);
  • 解决方案

    • 饿汉单例设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MySingleClass01 {

private MySingleClass01(){
System.out.println("饿汉");
if (null != instance) {
throw new RuntimeException("已经存在对象了!");
}
}
private final static MySingleClass01 instance = new MySingleClass01();

public static MySingleClass01 getInstance() {
return instance;
}
}
  • 双重锁单例设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MySingleClass02 {

private MySingleClass02(){
System.out.println("双重锁");
if (null != instance) {
throw new RuntimeException("已经存在对象了!");
}
}

private static MySingleClass02 instance = null;

public static MySingleClass02 getInstance() {
if (null == instance) {
synchronized (MySingleClass02.class) {
if (null == instance) {
instance = new MySingleClass02();
}
}
}
return instance;
}
}
  • 静态内部类单例设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MySingleClass03 {

private MySingleClass03(){
System.out.println("静态内部类");
if (null != MySingleClass03Holder.instance){
throw new RuntimeException("已经存在对象了!");
}
}

static class MySingleClass03Holder{//静态内部类:不会随着外部类的加载而加载。只有当静态内部类被使用了,才会初始化instance。

private final static MySingleClass03 instance = new MySingleClass03();

}

public static MySingleClass03 getInstance() {
return MySingleClass03Holder.instance;
}


}

03. 序列化攻击单例设计模式

  • 代码实现

    • 饿汉单例设计模式
1
2
3
4
5
6
7
8
9
10
11
//饿汉单例设计模式
MySingleClass01 instance01 = MySingleClass01.getInstance();
//将instance01对象写入到了obj.obj文件中保存
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.obj"));
oos.writeObject(instance01);
oos.close();
//将对象从obj.obj文件中读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.obj"));
Object instance02 = ois.readObject();

System.out.println(instance01 == instance02);
  • 双重锁单例设计模式
1
2
3
4
5
6
7
8
9
10
//双重锁单例设计模式
MySingleClass02 instance01 = MySingleClass02.getInstance();
//将instance01对象写入到了obj.obj文件中保存
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.obj"));
oos.writeObject(instance01);
oos.close();
//将对象从obj.obj文件中读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.obj"));
Object instance02 = ois.readObject();
System.out.println(instance01 == instance02);
  • 静态内部类单例设计模式
1
2
3
4
5
6
7
8
9
10
11
//静态内部类单例设计模式
MySingleClass03 instance01 = MySingleClass03.getInstance();
//将instance01对象写入到了obj.obj文件中保存
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.obj"));
oos.writeObject(instance01);
oos.close();
//将对象从obj.obj文件中读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.obj"));
Object instance02 = ois.readObject();

System.out.println(instance01 == instance02);
  • 存在的问题

    • 现有的单例设计模式无法避免序列化攻击

03-单例模式
https://janycode.github.io/2018/04/28/10_设计模式/03-单例模式/
作者
Jerry(姜源)
发布于
2018年4月28日
许可协议