经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
DesignPattern系列__10单例模式
来源:cnblogs  作者:本墨  时间:2019/8/8 8:54:03  对本文有异议

单例模式介绍

单例模式,是为了确保在整个软件体统中,某个类对象只有一个实例,并且该类通常会提供一个对外获取该实例的public方法(静态方法)。
比如日志、数据库连接池等对象,通常需要且只需要一个实例对象,这就会使用单例模式。

单例模式的八种模式

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(同步方法)
  • 懒汉式(同步代码块)
  • 懒汉式(双重检查)
  • 静态内部类
  • 枚举

下面依次来说明一下:

饿汉式(静态常量)

通常,我们创建一个对象的方式就是new,但是,当我们考虑只创建一个实例的时候,就应该禁止外部来通过new的方式进行创建。同时,由于无法使用new,你应该考虑提供一个获取单例对象的方式给别人。

思路

1.将构造器私有化(防止外部new,但是对反射还是有局限)
2.类的内部创建对象
3.对外提供一个获取实例静态的public方法

代码实现:

  1. public class Singleton1 {
  2. public static void main(String[] args) {
  3. HungrySingleton hungrySingleton = HungrySingleton.getInstance();
  4. HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
  5. System.out.println(hungrySingleton == hungrySingleton1);
  6. }
  7. }
  8. class HungrySingleton {
  9. //1.私有化构造器
  10. private HungrySingleton() {
  11. }
  12. // 2.类内部创建对象,因为步骤3是static的,
  13. // 所以实例对象是static的
  14. private final static HungrySingleton instance = new HungrySingleton();
  15. //3.对外提供一个获取对象的方法,
  16. // 因为调用方式的目的就是为了获取对象,
  17. // 所以该方法应该是static的。
  18. public static HungrySingleton getInstance() {
  19. return instance;
  20. }
  21. }

运行程序显示,我们的确只创建了一个对象实例。

小结

优点:代码实现比较简单,在类加载的时候就完成了实例化,同时,该方式能够避免线程安全问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
这种方式基于classloder机制避免了多线程的同步问题,不过, instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种, 因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果。
总结:这种单例模式可以使用,但是可能造成内存的浪费。

饿汉式(静态代码块)

该方式和第一种区别不大,只是将创建实例放在了静态代码块中。
由于无法使用new,你应该考虑提供一个获取单例对象的方式给别人。

思路

1.将构造器私有化(防止外部new,但是对反射还是有局限)
2.类的内部创建对象(通过静态代码块)
3.对外提供一个获取实例静态的public方法

代码实现:

  1. public class Singleton2 {
  2. public static void main(String[] args) {
  3. HungrySingleton hungrySingleton = HungrySingleton.getInstance();
  4. HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
  5. System.out.println(hungrySingleton == hungrySingleton1);
  6. }
  7. }
  8. class HungrySingleton {
  9. //1.私有化构造器
  10. private HungrySingleton() {
  11. }
  12. // 2.类内部创建对象,因为步骤3是static的,
  13. // 所以实例对象是static的
  14. private final static HungrySingleton instance;
  15. static {
  16. instance = new HungrySingleton();
  17. }
  18. //3.对外提供一个获取对象的方法,
  19. // 因为调用方式的目的就是为了获取对象,
  20. // 所以该方法应该是static的。
  21. public static HungrySingleton getInstance() {
  22. return instance;
  23. }
  24. }

小结

该方式只是将对象的创建放在静态代码块中,其优点和缺点与第一种方式完全一样。
总结:这种单例模式可以使用,但是可能造成内存的浪费。(同第一种)

懒汉式(线程不安全)

该方式的主要思想就是为了改善饿汉式的缺点,通过懒加载(在使用的时候再去加载),达到节约内存的目的。
由于无法使用new,你应该考虑提供一个获取单例对象的方式给别人。

思路

1.将构造器私有化(防止外部new,但是对反射还是有局限)
2.类的内部创建对象,懒加载,在使用的时候才去加载
3.对外提供一个获取实例静态的public方法

代码实现:

  1. public class Singleton3 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. class LazySingleton {
  11. //1.私有化构造器
  12. private LazySingleton() {}
  13. //2.类的内部声明对象
  14. private volatile static LazySingleton instance;
  15. //3.对外提供获取对象的方法
  16. public static LazySingleton getInstance() {
  17. //判断类是否被初始化
  18. if (instance == null) {
  19. //第一次使用的时候,创建对象
  20. instance = new LazySingleton();
  21. }
  22. return instance;
  23. }
  24. }
  25. class TestThread implements Runnable {
  26. @Override
  27. public void run() {
  28. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  29. try {
  30. //为了演示多线程情况
  31. Thread.sleep(100);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. LazySingleton instance = LazySingleton.getInstance();
  36. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  37. }
  38. }

执行程序后,发现了问题:

  1. //运行结果:
  2. 线程Thread-0开始执行
  3. 线程Thread-1开始执行
  4. 线程Thread-1初始化对象1391273746
  5. 线程Thread-0初始化对象547686109

小结

优点:起到了懒加载的作用,但是只能在单线程情况下使用。
缺点:多线程下不安全,如果一个线程进入到if语句中阻滞(还未开始创建对象),另一线程进入并通过了if判断,则会创建多个实例,这一点就违背了单例的目的。
结论:实际情况下,不要使用这种方式。

懒汉式(线程安全,同步方法)

思路

同上一中方式一样,但是为了解决多线程安全问题,使用同步方法。

代码演示:

  1. public class Singleton4 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. class LazySingleton {
  11. //1.私有化构造器
  12. private LazySingleton() {}
  13. //2.类的内部声明对象
  14. private volatile static LazySingleton instance;
  15. //3.对外提供获取对象的方法
  16. public synchronized static LazySingleton getInstance() {
  17. //判断类是否被初始化
  18. if (instance == null) {
  19. //第一次使用的时候,创建对象
  20. instance = new LazySingleton();
  21. }
  22. return instance;
  23. }
  24. }
  25. class TestThread implements Runnable {
  26. @Override
  27. public void run() {
  28. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  29. try {
  30. //为了演示多线程情况
  31. Thread.sleep(100);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. LazySingleton instance = LazySingleton.getInstance();
  36. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  37. }
  38. }

运行结果如下所示:

  1. 线程Thread-1开始执行
  2. 线程Thread-0开始执行
  3. 线程Thread-0初始化对象681022576
  4. 线程Thread-1初始化对象681022576

小结

优点:起到了懒加载的效果,同时,解决了线程安全问题。
缺点:效率低下,每次想要获取对象的时候,去执行getInstance()都是通过同步方法。而且,初始化对象后,再次使用的时候,应该直接return这个对象。
总结:可以在多线程条件下使用,但是效率低下,不推荐。

懒汉式(线程安全,同步代码块)

思路

同样是为了解决多线程安全问题,不过采用的是同步代码块。首先,最先想到的是:

1.将getInstance()方法体全部加上同步锁。

代码实现:

  1. public class Singleton5 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. //对getInstance()的方法体整体加同步代码块
  11. class LazySingleton {
  12. //1.私有化构造器
  13. private LazySingleton() {}
  14. //2.类的内部声明对象
  15. private volatile static LazySingleton instance;
  16. //3.对外提供获取对象的方法
  17. public static LazySingleton getInstance() {
  18. //同步代码块
  19. synchronized (LazySingleton.class) {
  20. //判断类是否被初始化
  21. if (instance == null) {
  22. //第一次使用的时候,创建对象
  23. instance = new LazySingleton();
  24. }
  25. }
  26. return instance;
  27. }
  28. }
  29. class TestThread implements Runnable {
  30. @Override
  31. public void run() {
  32. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  33. try {
  34. //为了演示多线程情况
  35. Thread.sleep(100);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. LazySingleton instance = LazySingleton.getInstance();
  40. // LazySingleton1 instance = LazySingleton1.getInstance();
  41. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  42. }
  43. }

运行的结果:

  1. 线程Thread-0开始执行
  2. 线程Thread-1开始执行
  3. 线程Thread-1初始化对象1419349448
  4. 线程Thread-0初始化对象1419349448

这种方式的优缺点和同步方法一样,能够实现多线程安全,但是效率低下。那么,能不能提高一下效率呢?我们发现,每次调用getInstance()的时候,都要进入同步代码块,但是,一旦对象初始化后,第二次使用的时候,应该能够直接获取这个对象才对。
按照这个思路,对代码进行更改(为了说明这个,新建一个类LazySingleton1):

2.只在初始化对象部分加上同步锁

代码实现:

  1. //为了提高效率,通过if判断,初始化之前进入同步锁
  2. class LazySingleton1 {
  3. //1.私有化构造器
  4. private LazySingleton1() {}
  5. //2.类的内部声明对象
  6. private volatile static LazySingleton1 instance;
  7. //3.对外提供获取对象的方法
  8. public static LazySingleton1 getInstance() {
  9. //判断类是否被初始化
  10. if (instance == null) {
  11. //第一次使用的时候,创建对象
  12. synchronized (LazySingleton1.class) {
  13. instance = new LazySingleton1();
  14. }
  15. }
  16. return instance;
  17. }

将类TestClass的run()方法进行更改,获取的实例改为LazySingleton1类型。代码看上去没有问题,那么运行效果如何呢:

  1. //运行结果:
  2. 线程Thread-1开始执行
  3. 线程Thread-0开始执行
  4. 线程Thread-1初始化对象1368942806
  5. 线程Thread-0初始化对象1187311731

那么,我们发现,打脸了,多线程情况下,创建了两个对象,并未达到单例的目的。

小结

  • 对整个方法体加同步代码块
    可以达到要求,优缺点同同步方法。
  • 只在初始化对象的代码添加同步锁
    不能满足线程安全要求,实际工作中,不能使用这种方式

懒汉式(线程安全,双重检查机制)

思路

针对懒汉式的多线程问题,我们可谓是操碎了心:同步方法可以解决问题,但是效率太低了;同步代码块则根本不能保证多线程安全。如何能做到“鱼和熊掌兼得”呢?既然同步代码块的效率较好,那么我们就针对这个方式进行改良:双重检查机制,即在getInstance()内进行两次检查,第一次通过if判断后,初始化对象之前,进行同步并再次进行判断。这样做的目的:既能解决线程安全问题,同时避免第二次使用对象的时候还要执行同步的代码。

代码实现:

  1. public class Singleton6 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. class LazyDoubleCheckSingleton {
  11. //1.私有化构造器
  12. private LazyDoubleCheckSingleton() {}
  13. //2.类的内部声明对象
  14. private volatile static LazyDoubleCheckSingleton instance;
  15. //3.对外提供获取对象的方法
  16. public static LazyDoubleCheckSingleton getInstance() {
  17. //判断类是否被初始化
  18. if (instance == null) {
  19. //第一次使用,通过if判断
  20. //加锁
  21. synchronized (LazyDoubleCheckSingleton.class) {
  22. //拿到锁后,初始化对象之前,再次进行判断
  23. if (instance == null) {
  24. instance = new LazyDoubleCheckSingleton();
  25. }
  26. }
  27. }
  28. return instance;
  29. }
  30. }
  31. class TestThread implements Runnable {
  32. @Override
  33. public void run() {
  34. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  35. try {
  36. //为了演示多线程情况
  37. Thread.sleep(100);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
  42. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  43. }
  44. }

运行结果如下所示:

  1. //运行结果:
  2. 线程Thread-0开始执行
  3. 线程Thread-1开始执行
  4. 线程Thread-1初始化对象996963733
  5. 线程Thread-0初始化对象996963733

小结

优点:

  • 解决了上一种方式中的线程安全问题,同时实现了延迟加载的效果,节约内存;
  • 第二次使用的时候,if判断为false,直接返回创建好的对象,避免进入同步代码,提高了效率;
    结论:推荐使用这种方式,实际工作中也比较常见这种方式。

静态内部类

思路

为了实现多线程情况下安全,除了手工加锁,还有别的方式。现在,我们采用静态内部类的方式。这种方式利用了JVM加载类的机制来保证只初始化一个对象。
思路同样是私有化构造器,对外提供静态的公开方法;不同之处是,类的创建交给静态内部类来时实现。

代码实现

  1. public class Singleton7 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. class StaticInnerSingleton {
  11. // 1.构造器私有化
  12. private StaticInnerSingleton() {}
  13. // 2.通过静态内部类来初始化对象
  14. private static class InnerClass {
  15. private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
  16. }
  17. // 3.对外提供获取对象的方法
  18. public static StaticInnerSingleton getInstance() {
  19. return InnerClass.INSTANCE;
  20. }
  21. }
  22. class TestThread implements Runnable {
  23. @Override
  24. public void run() {
  25. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  26. try {
  27. //为了演示多线程情况
  28. Thread.sleep(100);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. StaticInnerSingleton instance = StaticInnerSingleton.getInstance();
  33. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  34. }
  35. }

运行结果:

  1. 线程Thread-0开始执行
  2. 线程Thread-1开始执行
  3. 线程Thread-0初始化对象1326533480
  4. 线程Thread-1初始化对象1326533480

OK,我们发现,这种方式达到了预期的效果。

小结

优点:

  • 这种静态内部类的方式,通过类加载机制来保证了初始化实例时只有一个实例。
  • 类的静态属性只有在第一次加载类的时候初始化,而JVM能保证线程安全,在类的初始化过程中,只有一个线程能进入并完成初始化。
  • 静态内部类方式实现了懒加载的效果,这种方式不会在类StaticInnerSingleton加载的时候进行初始化,而是在第一次使用时调用getInstance()方法初始化,能够起到节约内次的目的。
  • 该方式的getInstance()方法,通过调用静态内部类的静态属性返回实例对象,避免了每次调用时进行同步,效率高。
    结论:线程安全,效率高,代码实现简单,推荐使用。

枚举

思路

在静态内部类的方式中,我们借用了JVM的类加载机制来实现了功能,同样,还可以借用Java的枚举来实现单例模式。

  1. public class Singleton8 {
  2. public static void main(String[] args) {
  3. TestThread testThread = new TestThread();
  4. Thread thread = new Thread(testThread);
  5. Thread thread1 = new Thread(testThread);
  6. thread.start();
  7. thread1.start();
  8. }
  9. }
  10. enum EnumSingleton {
  11. INSTANCE;
  12. public void sayHi() {
  13. System.out.println("Hi, " + INSTANCE);
  14. }
  15. }
  16. class TestThread implements Runnable {
  17. @Override
  18. public void run() {
  19. System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
  20. try {
  21. //为了演示多线程情况
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. EnumSingleton instance = EnumSingleton.INSTANCE;
  27. System.out.println("线程" + Thread.currentThread().getName() + "初始化对象" + instance.hashCode());
  28. }
  29. }

运行结果如下:

  1. 线程Thread-0开始执行
  2. 线程Thread-1开始执行
  3. 线程Thread-1初始化对象1134798663
  4. 线程Thread-0初始化对象1134798663

小结

优点:这中方式需要在JDK1.5以上的版本中使用,利用枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。在《Effective Java》中提到了这种方式,其作者推荐。
结论:推荐使用。

单例模式的序列化漏洞

在上面的枚举类的总结中,我们提高枚举方式能够避免反序列化对象的时候重新建立新的对象(反序列化漏洞),那么什么是反序列化漏洞呢?Java对象进行反序列化的时候会通过反射机制来创建实例,反射机制的存在使得我们可以越过Java本身的静态检查和类型约束,在运行期直接访问和修改目标对象的属性和状态。这里理解的不是很准确,有错误的话请指出。

代码演示:

  1. public class Test {
  2. public static void main(String[] args) throws IOException, ClassNotFoundException {
  3. // HungrySingleton instance = HungrySingleton.getInstance();
  4. // //序列化
  5. // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serializable_singleton"));
  6. // oos.writeObject(instance);
  7. //
  8. // //反序列化
  9. // ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serializable_singleton"));
  10. // HungrySingleton newInstance = (HungrySingleton) ois.readObject();
  11. LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
  12. //序列化
  13. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serializable_singleton"));
  14. oos.writeObject(instance);
  15. //反序列化
  16. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serializable_singleton"));
  17. LazyDoubleCheckSingleton newInstance = (LazyDoubleCheckSingleton) ois.readObject();
  18. System.out.println(instance);
  19. System.out.println(newInstance);
  20. System.out.println(instance == newInstance);
  21. }
  22. }
  23. class HungrySingleton implements Serializable {
  24. private static final long serialVersionUID = -4913346286867374832L;
  25. //1.私有化构造器
  26. private HungrySingleton() {
  27. }
  28. // 2.类内部创建对象,因为步骤3是static的,
  29. // 所以实例对象是static的
  30. private final static HungrySingleton instance;
  31. static {
  32. instance = new HungrySingleton();
  33. }
  34. //3.对外提供一个获取对象的方法,
  35. // 因为调用方式的目的就是为了获取对象,
  36. // 所以该方法应该是static的。
  37. public static HungrySingleton getInstance() {
  38. return instance;
  39. }
  40. //解决单例模式的反序列化漏洞
  41. // public Object readResolve() {
  42. // return instance;
  43. // }
  44. }
  45. class LazyDoubleCheckSingleton implements Serializable {
  46. private static final long serialVersionUID = -8459475238793042042L;
  47. //1.私有化构造器
  48. private LazyDoubleCheckSingleton() {}
  49. //2.类的内部声明对象
  50. private volatile static LazyDoubleCheckSingleton instance;
  51. //3.对外提供获取对象的方法
  52. public static LazyDoubleCheckSingleton getInstance() {
  53. //判断类是否被初始化
  54. if (instance == null) {
  55. //第一次使用,通过if判断
  56. //加锁
  57. synchronized (LazyDoubleCheckSingleton.class) {
  58. //拿到锁后,初始化对象之前,再次进行判断
  59. if (instance == null) {
  60. instance = new LazyDoubleCheckSingleton();
  61. }
  62. }
  63. }
  64. return instance;
  65. }
  66. // public Object readResolve() {
  67. // return instance;
  68. // }
  69. }

这里,我们分别提供了懒汉式和饿汉式(双重检查)来验证这个现象。运行后会报错,实现Serializable接口后能够正常运行,结果如下:

  1. com.bm.desginpattern.pattern.creational.singleton.serialization.LazyDoubleCheckSingleton@7f31245a
  2. com.bm.desginpattern.pattern.creational.singleton.serialization.LazyDoubleCheckSingleton@6d03e736
  3. false

创建了两个对象,没有实现多线程安全。首先说明一下解决方案,然后再讲解一下原理。我们发现饿汉式还是懒汉式都新增了一个方法readResolve(),将注释取消后,再次运行的结果如下:

  1. com.bm.desginpattern.pattern.creational.singleton.serialization.LazyDoubleCheckSingleton@7f31245a
  2. com.bm.desginpattern.pattern.creational.singleton.serialization.LazyDoubleCheckSingleton@7f31245a
  3. true

奇迹出现了,只是增加一个方法,情况完全不同了。那么背后的原理是什么呢?我们通过debug来讲解:

1.在23行打一个断点,进入并进入该方法:

2.我们发现,该方法首先是进行一些判断,然后执行readObject0()方法,进入该方法查看:

  1. //该方法完成代码
  2. private Object readObject0(boolean unshared) throws IOException {
  3. boolean oldMode = bin.getBlockDataMode();
  4. if (oldMode) {
  5. int remain = bin.currentBlockRemaining();
  6. if (remain > 0) {
  7. throw new OptionalDataException(remain);
  8. } else if (defaultDataEnd) {
  9. /*
  10. * Fix for 4360508: stream is currently at the end of a field
  11. * value block written via default serialization; since there
  12. * is no terminating TC_ENDBLOCKDATA tag, simulate
  13. * end-of-custom-data behavior explicitly.
  14. */
  15. throw new OptionalDataException(true);
  16. }
  17. bin.setBlockDataMode(false);
  18. }
  19. byte tc;
  20. while ((tc = bin.peekByte()) == TC_RESET) {
  21. bin.readByte();
  22. handleReset();
  23. }
  24. depth++;
  25. totalObjectRefs++;
  26. try {
  27. switch (tc) {
  28. case TC_NULL:
  29. return readNull();
  30. case TC_REFERENCE:
  31. return readHandle(unshared);
  32. case TC_CLASS:
  33. return readClass(unshared);
  34. case TC_CLASSDESC:
  35. case TC_PROXYCLASSDESC:
  36. return readClassDesc(unshared);
  37. case TC_STRING:
  38. case TC_LONGSTRING:
  39. return checkResolve(readString(unshared));
  40. case TC_ARRAY:
  41. return checkResolve(readArray(unshared));
  42. case TC_ENUM:
  43. return checkResolve(readEnum(unshared));
  44. case TC_OBJECT:
  45. return checkResolve(readOrdinaryObject(unshared));
  46. case TC_EXCEPTION:
  47. IOException ex = readFatalException();
  48. throw new WriteAbortedException("writing aborted", ex);
  49. case TC_BLOCKDATA:
  50. case TC_BLOCKDATALONG:
  51. if (oldMode) {
  52. bin.setBlockDataMode(true);
  53. bin.peek(); // force header read
  54. throw new OptionalDataException(
  55. bin.currentBlockRemaining());
  56. } else {
  57. throw new StreamCorruptedException(
  58. "unexpected block data");
  59. }
  60. case TC_ENDBLOCKDATA:
  61. if (oldMode) {
  62. throw new OptionalDataException(true);
  63. } else {
  64. throw new StreamCorruptedException(
  65. "unexpected end of block data");
  66. }
  67. default:
  68. throw new StreamCorruptedException(
  69. String.format("invalid type code: %02X", tc));
  70. }
  71. } finally {
  72. depth--;
  73. bin.setBlockDataMode(oldMode);
  74. }
  75. }

我们发现,该方法还是对传入的对象进行一些判断,在这里,我们匹配到TC_OBJECT,执行对应的方法。
3.进入该方法:

4.进一步查看:

我们看到一个名为resolveEx的属性,说明很接近了。
5.继续往下调试:


我们发现,这三个条件都满足,因为我们在LazyDoubleCheckSingleton类中定义了readResolve()方法。

6.if判断通过,进入到下一个方法:

7.在该方法中,我们发现经过一些条件判断后,通过反射方式来调用我们在类LazyDoubleCheckSingleton中新定义的方法readResolve():

  • 如果我们没有新增这个方法,反射的时候会新建一个LazyDoubleCheckSingleton对象,并将其返回;
  • 当我们新增这个readResolve()的时候,反射的时候还是会创建一个新的对象,但是,返回的是我们在readResolve()中的定义的返回对象。从而达到了多线程安全的目的。

单例模式的反射

未完待续

原文链接:http://www.cnblogs.com/JackHou/p/11317284.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号