剑指offer2-Singleton模式

题目

设计一个类,我们只能生成该类的一个实例

单例模式

定义:“一个类只有一个实例,并且自行实例化向整个系统提供”

“保证一个类仅有一个实例,并提供一个访问它的全局访问点

通过定义,我们可以得出在单例模式,需要我们注意的方面在于:

1.生成实例的代码只能执行一次,以此保证生成类的实例唯一。同时构造函数应当设置为私有的,从而禁止他人创建实例(那么如果构造方法私有化,外部无法调用该类通过new进行实例化,因此在类中应该先将该对象实例化)。

2.并且定义一个静态static的实例,在需要时创建该实例。

  1)静态方法可以直接调用同类中的静态成员,而不能直接调用非静态成员。由于静态成员在类装载时装载的,即对象创建之前就已经写入内存了。

  2)调用非静态成员时,需要通过创建该类的对象来访问。

  3)而普通成员方法(非静态方法)中,则可以直接访问同类的非静态变量和静态变量。

  4)静态方法和静态对象仅属于类本身,而不属于类的一个对象。

  5)静态方法可以在使用非常频繁的时候应用,可以提高系统性能。

  6)可以用于定义对象中不会变的属性

3.final关键字。修饰引用、方法和类

  1)修饰类:该类为最终类,无法被继承

  2)修饰方法:为最终方法,不能被子类重写,但是可以被继承

  3)修饰引用:必须初始化,且无法被修改。如果引用为引用数据类型,引用的地址的值不可以修改,但是引用所指向的对象里面的内容可以修改。

4.JVM内部的机制能保证当一个类被加载时,这个类的加载过程是线程互斥的。

解法

1.存在线程安全问题的方法。如果线程A执行到了new方法,此时对象还没创建,而出现一个线程B执行到判断sin==null,条件为true,也会进入到创建实例的部分。内存中就将出现两个实例。

public class Singleton1{
  private static Singleton1 sin=null;
  private Singleton1()
    {}                  
  public static Singleton1 getSingleton1(){
        if(sin==null){
                sin=new Singleton1();
        }
        return sin;    
    }  
}

 2.为了解决以上问题,由于一个时刻只能有一个线程获得同步锁,我们可以选择在获取单例对象的方法上加上一个synchronized关键字,但加锁是非常耗时的。。会导致效率过差,由于如果第一个线程占用锁,然后实例化之后释放锁,那么第二个线程又会加锁,即使之后的判断实例已经被创建,不能再实例化。即后面的线程都并发走到加锁这里,每次获取实例,都会试图加上一个同步锁。

public class Singleton2{
  private static Singleton2 sin=null;
  private Singleton2()
    {}                  
  public static synchronized Singleton2 getSingleton2(){
        if(sin==null){
                sin=new Singleton2();
        }
        return sin;    
    }  
}

3.为了解决以上问题,我们可以不在方法上加锁,而在方法内部加锁。这样时间效率要好很多。

public class Singleton3{
  private static Singleton3 sin=null;
  private Singleton3()
    {}                  
  public static Singleton3 getSingleton3(){
        if(sin==null){//若该判断不为空,那么说明对象已经被创建过了,直接返回该对象即可,不会走到同步锁上,不浪费多余的资源。从而不影响性能
            synchronized (Singleton3.class){
            if(sin==null){//如果上一个判断为空,那么会有很多创建对象的线程通过第一层判断。那么即使第二层某个线程执行完,释放了锁,还是会有其他线程进入到同步锁这一块。为了防止多次创建,所以再加一层判断。
                      sin=new Singleton3();
            }
        }
        }
        return sin;    
    }  
}

 4.不是延迟加载,而是在类创建的时候,就把相应的实例创建好了。但是如果在获取对象的时候需要传一个参数,那么就不能使用这种方法&在类加载时就初始化,会容易产生垃圾对象,浪费内存。(饿汉式)

public class Singleton4{
//创建Singleton4的一个对象
private static Singleton4 sin=new Singleton4();
//让构造函数为private,这样该类不会被实例化
private Singleton4() {}
//获取唯一可用的对象
public static Singleton4 getSingleton4(){ return sin; } }

5.通过静态内部类实现,对静态域使用延迟初始化。利用classloader机制来保证初始化instance时只有一个线程。由于SingletonHolder类没有被主动使用,只用通过显式调用getInstance()方法时,才会显式装载SingletonHolder类,从而实例化INSTANCE。

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

 6.枚举。枚举对象就是天生单例,客户端不允许创建枚举类的实例,也不能对其进行扩展。可以解决私有化构造器并不保险,序列化问题。

public enum Singleton6{
  //创建一个枚举对象,该对象天生为单例   INSTANCE;
  //对外暴露一个获取该对象的接口   
public Singleton6 getInstance(){
    return INSTANCE;   } }

扩展--设计模式(23种)

1.设计模式根据意图或目的分类,主要覆盖三种模式类别:创建者/结构型/行为

2.创建者模式:提供创建对象的机制,增加已有代码的灵活性和可复用性

  1)工厂方法模式:在父类中提供一个创建对象的接口,允许子类决定实例化对象的类型,创建对象时不会对客户端暴露创建逻辑。

   2)抽象工厂模式能创建一系列相关的对象,而无需指定其具体类。对于系列产品的每个变体,都基于抽象工厂接口创建不同的工厂类,每个工厂类都只能返回特定类别的产品。

  不修改原工厂的代码,直接进行扩展即可,不关心构造对象实例的细节和复杂过程,且构造的实例过程比较

   3)生成器模式:将对象构造代码从产品类中抽象出来,并将其放在一个名为生成器的独立对象中。

   4)原型模式:能够复制已有对象,而又无需使代码依赖它们所属的类。原型模式将克隆过程委派给被克隆的实际对象,模式为所有支持克隆的对象声明一个通用接口,该接口用于克隆对象,而无需将代码和对象所属类耦合。

 

   5)单例模式

3.结构型模式:介绍如何将对象或类组装成较大的结构,并同时保持结构的灵活和高效

   1)适配器模式:使接口不兼容的对象能够互相合作。适配器实现了其中一个对象的接口,并对另一个对象进行封装,适配器接受客户端通过适配器接口发起的调用,并将其转换为适用于被封装服务对象的调用。

   2)桥接模式:可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而在开发时分别使用。

   3)组合模式:将对象组合成树状结构,并且能想使用独立对象一样使用它们。以递归方式处理对象树中的所有项目

   4)装饰模式:允许将对象放入包含行为的特殊封装对象中,来为原对象绑定新的行为。也称为“封装器”,是一个能与其他“目标”对象连接的对象,包含与目标对象相同的一系列方法,它会将所有接收到的请求委派给目标对象。

   5)外观模式:能为程序库、框架或其他复杂类提供一个简单的接口。

  5)代理模式:能够提供对象的代替品或其占位符,代理控制着对原对象的访问,以及允许将请求提交给对象前后进行一些处理。(如windows里面的快捷方式、Spring AOP,即通过在目标类的基础上增加切面逻辑,完成一些和程序业务无关的内容)

  新建一个与原服务对象接口相同的代理类,然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象,并将所有工作委派给它。

  6)享元模式:摒弃在每个对象中保存所有数据的方式,通过共享夺各对象所共有的相同状态,从而在有限的内存容量中载入更多对象。

4.行为模式:负责对象间的高效沟通和权责委派

  1)责任链模式:允许将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

  如jsp servlet中的Filter,Struts2的拦截器

  2)命令模式:将请求转换为一个包含与请求相关的所有信息的独立对象。该转换能根据不同请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。

  3)迭代器模式:在不暴露底层表现形式的情况下遍历集合中所有的元素。

  4)中介者模式:减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。

  即停止组件之间的直接交流并使其相互独立,这些组件必须调用特殊的中介者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。

  5)备忘录模式:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

  6)观察者模式:定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。

  拥有一些值得关注的状态的对象通常被称为目标, 由于它要将自身的状态改变通知给其他对象, 我们也将其称为发布者,所有希望关注发布者状态变化的其他对象被称为订阅者。

  7)状态模式:能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

  8)策略模式:定义一系列算法 并将每种算法分别放入独立的类中 以使算法的对象能够相互替换

  找出负责用许多不同方式完成特定任务的类 然后将其中的算法抽取到一组被称为策略的独立类中

  9)模板方法模式:在超类中定义了一个算法的框架 允许子类在不修改结构的情况下重写算法的特定步骤

  10)访问者模式:将算法与其所作用的对象隔离开。

 该部分图源:https://refactoringguru.cn/design-patterns,侵删~

猜你喜欢

转载自www.cnblogs.com/lyeeer/p/12180553.html