设计模式之美-1 |单例模式

一、思考题

  1. 单例的几种设计模式?
  2. 单例存在哪些问题
  3. 单例与静态类的区别
  4. 有什么替代的解决方案?

二、单例的几种设计模式

单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

一.懒汉模式(即时加载)

package designpattern.singleton;


public class Mgr01 {
    
    

    private static final Mgr01 INSTANCE = new Mgr01();

    private Mgr01() {
    
    
    }

    public static Mgr01 getInstance() {
    
    
        return INSTANCE;
    }

    public void m() {
    
    
        System.out.println("m");
    }

    public static void main(String[] args) {
    
    
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}

二.饿汉模式(延迟加载)

package designpattern.singleton;

/**

 * Description:比较好的写法,双重检测。
 */
public class Mgr06 {
    
    
    //volatile避免指令重排
    private static volatile Mgr06 INSTANCE;

    private Mgr06() {
    
    
    }

    public static Mgr06 getInstance() {
    
    
        if (INSTANCE == null) {
    
    
            //减少同步代码块,试图提升效率,结果是失败的。
            synchronized (Mgr06.class) {
    
    
                //双重判断
                if (INSTANCE == null) {
    
    
                    try {
    
    
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
    
    
        System.out.println("m");
    }


    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Mgr06.getInstance().hashCode());
                }
            }).start();
        }
    }
}

三.静态内部类

package designpattern.singleton;

/**
 * Description: 静态内部类,比较完善的写法,看自己喜欢。
 */
public class Mgr07 {
    
    

    private Mgr07() {
    
    
    }

    private static class Mgr07Holder {
    
    
        private final static Mgr07 INSTANCE = new Mgr07();
    }
    private static Mgr07 getInstance() {
    
    
        return Mgr07Holder.INSTANCE;
    }
    public void m() {
    
    
        System.out.println("m");
    }


    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Mgr07.getInstance().hashCode());
                }
            }).start();
        }
    }
}

四.枚举

package designpattern.singleton;

/**

 * Description:大神写的,不仅可以解决线程同步,还可以防止反序列化。
 */
public enum Mgr08 {
    
    
    INSTANCE;

    public void m() {
    
    
        System.out.println("m");
    }


    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Mgr08.INSTANCE.hashCode());
                }
            }).start();
        }
    }
}

三、单例存在的问题

大部分情况下,我们在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID 生成器类。单例模式书写简洁、使用方便,在代码中,我们不需要创建对象,直接通过类似 IdGenerator.getInstance().getId() 这样的方法来调用就可以了。但是,这种使用方法有点类似硬编码(hard code),会带来诸多问题。接下来,我们就具体看看到底有哪些问题。

一.单例对 OOP 特性的支持不友好

二.单例会隐藏类之间的依赖关系(不方便阅读源码)

通过构造函数、参数传递等方式声明的类之间的依赖关系,我们通过查看函数的定义,就能很容易识别出来。但是,单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。在阅读代码的时候,我们就需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。

三.单例对代码的扩展性不友好(慎重使用单例,只有那些真的不需要也不会扩展的类使用单例比较好)

单例类只能创建一个实例对象,需要扩展就会很复杂。

四.单例对代码的可测试性不友好

如果单例类持有成员变量(比如 IdGenerator 中的 id 成员变量),那它实际上相当于一种全局变量,被所有的代码共享。如果这个全局变量是一个可变全局变量,也就是说,它的成员变量是可以被修改的,那我们在编写单元测试的时候,还需要注意不同测试用例之间,修改了单例类中的同一个成员变量的值,从而导致测试结果互相影响的问题。

五.单例不支持有参数的构造函数

如果带参数就会这样,第一次传递进去之后,第二遍传递进去的并不生效,会有误导。

Singleton singleton1 = Singleton.getInstance(10, 50);
Singleton singleton2 = Singleton.getInstance(20, 30);

比较好的解决方案是,参数放到另外一个全局变量中。


public class Config {
    
    
  public static final int PARAM_A = 123;
  public static final int PARAM_B = 245;
}

public class Singleton {
    
    
  private static Singleton instance = null;
  private final int paramA;
  private final int paramB;

  private Singleton() {
    
    
    this.paramA = Config.PARAM_A;
    this.paramB = Config.PARAM_B;
  }

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

四、替代单例的方案

业务上有表示全局唯一类的需求,如果不用单例,怎么才能保证这个类的对象全局唯一。
为了保证全局唯一,除了使用单例,还可以用静态方法来实现。这也是项目开发中经常用到的一种实现思路。不过,静态方法这种实现思路,它比单例更加不灵活,比如无法支持延迟加载。

五、单例的唯一性

线程唯一、进程唯一、集群唯一
“进程唯一”指的是进程内唯一、进程间不唯一。“线程唯一”指的是线程内唯一、线程间不唯一。集群相当于多个进程构成的一个集合,“集群唯一”就相当于是进程内唯一、进程间也唯一。也就是说,不同的进程间共享同一个对象,不能创建同一个类的多个对象。

一.单例的唯一性(进程唯一,一个程序有且仅有的一个对象)

单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”
定义中提到,“一个类只允许创建唯一一个对象”。那对象的唯一性的作用范围是什么呢?是指线程内只允许创建一个对象,还是指进程内只允许创建一个对象?答案是后者,也就是说,单例模式创建的对象是进程唯一的。

二.如何实现线程唯一的单例?(线程唯一)

三.如何实现集群环境下的单例?(集群唯一)

四.如何实现一个多例模式?

猜你喜欢

转载自blog.csdn.net/qq_38173650/article/details/114697573