设计模式

150 阅读9分钟

今天面试让手写单例模式,本以为很顺畅的写完,结果下笔动手还是要想,印象不深刻,now 重新整理下单例的几种写法。不过今天面试面的还是很happy的。。。。

引:如何防止反射机制和序列化反序列化破坏单例模式

1、懒汉式,线程安全  

**是否 Lazy 初始化:**是

**是否多线程安全:**是

public class Singleton {
   private static Singleton instance;
   private Singleton (){}
   public static synchronized Singleton getInstance() {
      if (instance == null) {
         instance = new Singleton();
      }
      return instance;
   }
}

2、双检锁/双重校验锁

**是否 Lazy 初始化:**是

**是否多线程安全:**是

public class Singleton {
   private volatile static Singleton singleton;
   private Singleton (){}
   public static Singleton getSingleton() {
      if (singleton == null) {
         synchronized (Singleton.class) {
            if (singleton == null) {
               singleton = new Singleton();
            }
         }
      }
      return singleton;
   }
}

3、饿汉式

**是否 Lazy 初始化:**否     instance 在类装载时就实例化

**是否多线程安全:**是

public class Singleton {
   private static Singleton instance = new Singleton();
   private Singleton (){}
   public static Singleton getInstance() {
      return instance;
   }
}

只要Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果)

4、静态内部类

**是否 Lazy 初始化:**是

**是否多线程安全:**是

public class Singleton {
   private static class SingletonHolder {
      private static final Singleton INSTANCE = new Singleton();
   }
   private Singleton (){}
   public static final Singleton getInstance() {
      return SingletonHolder.INSTANCE;
   }
}


这种方式是 Singleton 类被装载了,instance 不一定被初始化。
因为 SingletonHolder 类没有被主动使用,
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

//当第一次加载Singleton类时并不会初始化 Instance,只有在第一次调用Singleton的getInstance方法时才会导致Instance被初始化。 

//因此第一次调用getInstance方法会导致虚拟机加载SingletonHolder类, 

//这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时延迟了单例的实例化。 

//原因:虚拟机会保证一个类的构造器()方法在多线程环境中被正确地加载,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的 构造器()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。

特别需要注意的是,在这种情形下,其他线程虽然会被阻塞,但如果执行()方法的那条线程退出后,其他线程在唤醒之后不会再次进入/执行()方法,因为 在同一个类加载器下,一个类型只会被初始化一次。如果在一个类的()方法中有耗时很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的 

 原文:https://blog.csdn.net/chuixue24/article/details/80092510 

补充:

  • 1、调用外部类的静态变量,静态方法可让外部类得到加载,不过静态内部类没有被加载;

  • 2、静态修饰过后的一切物件都只与类有关,不与对象引用相关

  • 3、静态变量,静态方法,静态块都是类级别的属性,而不是单纯的对象属性。他们在类第一次被使用时被加载(是一次使用,并不一定是实例化),可通过类变量、类方法调用他们。

  • 4、静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类的过程中也会加载外部类

5、枚举

**是否 Lazy 初始化:**否

**是否多线程安全:**是

它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

public enum Singleton {
   INSTANCE;
   public void whateverMethod() {
   }
}

扩展:

序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
在序列化期间,对象将其当前状态写入到临时或持久性存储区。
以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

看重点: 以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
也就是说如果你的单例实现了Serializable,反序列化出来的对象,是重新创建的对象了。

解决方法,单例类中增加readResolve()方法,可以避免实例重复 

原因: 可以通过查看readObject()源码,看看是如何反序列化创建对象的,这个方法创建完对象之后会通过反射机制判断类中是否有readResolve()方法,如果有readResolve()方法,会通过反射机制调用这个方法。所以当你在单例类中写上readResolve()方法,是能够保证得到的同一个单例的,能够保证单例的全局唯一性。

补充: 但是避免不了序列化重复创建对象,实际上我们这种写法只是将反序列化创建的对象覆盖掉了,在执行过程中JVM还是创建了新的对象。

-----------------------------------------------------------------------------------------------

二、适配器模式

**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

总结一下三种适配器模式的应用场景:

  1. 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。

  2. 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。

  3. 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。

三、装饰者模式

Decorator给一个对象Source动态的增加一些新的功能,要求装饰对象和被装饰对象实现同一个接口,装饰对象(Decorator)持有被装饰对象(Source)的实例。 

装饰器模式的应用场景:

  1. 需要扩展一个类的功能。

  2. 动态的为一个对象增加功能,而且还能动态撤销。

  3. 缺点:产生过多相似的对象,不易排错!

代理模式和装饰者的区别:

  • 装饰器模式关注于在一个对象上动态的添加方法,将原始对象作为一个参数传给装饰者的构造器,这样能够在运行时递归地构造装饰者。

  • 代理模式关注于控制对对象的访问。对客户隐藏一个对象的具体信息。我们常常在一个代理类中创建一个对象的实例,这样就在编译时确定了代理和真实对象之间的关系。

四、代理模式

  • 动态代理原理相关

  • 代理模式就是多一个代理类出来,替原对象进行一些操作。例如:房屋中介

**意图:**为其他对象提供一种代理以控制对这个对象的访问。

**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

**何时使用:**想在访问一个类时做一些控制。

五、观察者模式

包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承。观察者模式类似于邮件订阅和RSS订阅。

六、责任链模式

**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

**何时使用:**在处理消息的时候以过滤很多道。

有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。

职责链模式的主要优点

  1. 对象仅需知道该请求会被处理即可,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度

  2. 处理对象仅需维持一个指向其后继对象的引用,而不需要维持它对所有处理者的引用,可简化对象的相互连接

  3. 在给对象分派职责时,职责链可以更灵活性,可以在运行时对该链进行动态的增删改,改变处理一个请求的职责

  4. 新增一个新的具体请求处理者时无须修改原有代码,只需要在客户端重新建链即可,符合 "开闭原则"

职责链模式的主要缺点

  1. 一个请求可能因职责链没有被正确配置而得不到处理

  2. 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,且不方便调试

  3. 可能因为职责链创建不当,造成循环调用,导致系统陷入死循环

适用场景

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的

  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求

  3. 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序