设计模式之“对象性能模式”: Singleton 单例模式(笔记)

675 阅读4分钟

对象创建模式之“单例模式Singleton”


动机不纯

  • 在软件系统中会有一些特殊的类,需要保证在系统中只有一个实例,以保证他们的逻辑正确性、良好的效率
  • 绕过常规的构造器(Client不能直接new),提供一种机制保证一个类只有一个实例(任何地方,不同类之中使用的都是同一个对象指向同一个内存)?
  • 责任:类设计者,而非Client使用者。

Singleton模式定义

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

模式结构类图

要点

  1. Singleton模式中的实例构造器可以设置为protected以允许子类派生(但要保证子类也是singleton)
  2. Single模式一般不要支持拷贝构造函数、Clone接口(工厂模式),因为会导致多个实例对象,与Singleton模式违背
  3. 多线程下的Singleton模式的线程安全保证(不因为线程抢占问题让两个线程都同了null判断,导致进行了两次new)?--- 双检查锁的正确实现(使用)

最佳实践

单线程

public class Singleton {

    private static Singleton instance;

    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null ) {

            instance = new Singleton();
        }

        return instance;
    }
}


//  Singleton s1=Singleton.getInstance();

//  Singleton s2=Singleton.getInstance();

//  Singleton s3=Singleton.getInstance();

多线程1:同步锁 - 解决线程安全;双重判断 - 解决线程锁的性能问题

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() { }

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

        return instance;
    }
}

/*
1. 先分配内存
2. 执行构造器
3. 将内存地址复制给instance
 */

/*
1. 先分配内存
2. 将内存地址复制给instance
3. 执行构造器
 */

多线程2:静态变量的方式解决线程安全问题

public class Singleton {
    private Singleton() { }

    //lazy load
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

注意拾遗

  1. 静态对象(变量)存在哪里? static block,只有单线程能进去--->保证线程安全 java程序在内存中的存储分配情况: 堆区:  1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)  2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身  栈区: ** 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中  2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。  3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。  方法区:  1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量**。  2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
  2. 多线程的情况:线程不安全---> 抢占线程的情况,但被判断的变量为null时,可能两个线程都会通过,对象就会被new两次,破坏了单例模式
  3. 编译 reorder :使用volatile,保证以下代码编译结果为情况1(java 5.0才加入的volatile关键字机制)

Obj o = new Obj(); 情况1: 1 先分配内存 2 执行构造器 3 将内存地址复制给instance 情况2: 1 先分配内存 2 将内存地址复制给instance 3 执行构造器

  1. lazy load 懒加载 --- 晚加载:变量到了用到的时候才加载(被调用时) 在Client程序中,只要用到某个类,ClassLoader就会把类中的静态变量都加载到内存中,这样静态变量是无法晚加载的,但是被加载的类的内部类是不会被立刻加载,直到执行到主类中调用到该内部类的代码时才会被加载。 我们可以通过这种机制,实现一些变量的懒加载 --- 创建内部类,把变量放到该内部类中