参考资料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); System.out.println(s11); } }
class Singleton1 { private static final Singleton1 instance = new Singleton1(); private Singleton1() {} public static Singleton1 getInstance() { return instance; } }
|
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); System.out.println(s22); } }
class Singleton2 { private static Singleton2 instance = null; private Singleton2() {} 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() { 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); System.out.println(s33); } }
class Singleton3 { 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; } }
|
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);
|
- 通过查看 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);
|
-
-
结论
序列化攻击枚举单例设计模式
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);
|
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) { 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"); } }
|
上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的。
02. 反射攻击单例设计模式
所学的单例设计模式
- 饿汉单例设计模式(推荐用)
- 懒汉单例设计模式
- 同步懒汉单例设计模式
- 双重锁单例设计模式(推荐用)
- 静态内部类单例设计模式(推荐用)
存在的问题
无法避免反射攻击
反射攻击单例设计模式
- 饿汉单例设计模式
- 双重锁单例设计模式
- 静态内部类单例设计模式
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();
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();
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();
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{
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();
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();
System.out.println(instance01 == instance02);
|
1 2 3 4 5 6 7 8 9 10
| MySingleClass02 instance01 = MySingleClass02.getInstance();
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(); System.out.println(instance01 == instance02);
|
1 2 3 4 5 6 7 8 9 10 11
| MySingleClass03 instance01 = MySingleClass03.getInstance();
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();
System.out.println(instance01 == instance02);
|