单例模式

说明: 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例.

1.懒汉式,线程不安全

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

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

2.双重检验锁,线程安全

public class Singleton {
    /**
     * 对保存实例的变量添加volatile的修饰
     */
    private volatile static Singleton instance = null;
    private Singleton (){}

    public static Singleton getInstance() {
        // 先检查实例是否存在,如果不存在才进入下面的同步块
        if (instance == null) {
            // 同步块,线程安全的创建实例
            synchronized (Singleton.class) {
                // 再次检查实例是否存在,如果不存在才真的创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

使用volatile关键字的原因: 禁止指令重排序优化

如果没有volatile关键字,JVM在执行instance = new Singleton()的时候,具体的执行内容:

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

在JVM的即时编译器中存在指令重排序的优化.也就是说最终的执行顺序可能是1-3-2.在3执行完毕、2未执行之前,被线程二抢占了,这时 instance 已经是非null了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错.

3.饿汉式,线程安全

public class Singleton{
    // 4: 定义一个静态变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次
    private static final Singleton instance = new Singleton();

    //1: 私有化构造方法,好在内部控制创建实例的数目
    private Singleton(){}

    //2: 定义一个方法来为客户端提供类实例
    //3: 这个方法需要定义成类方法,也就是要加static
    public static Singleton getInstance(){
        return instance;
    }
}

对比

  • 时间和空间:
    • 懒汉式是典型的时间换空间
    • 饿汉式是典型的空间换时间
  • 线程安全:
    • 不加同步的懒汉式是线程不安全的
    • 饿汉式是线程安全的,因为虚拟机保证每个静态成员变量只会被当前的ClassLoader加载一次

4.静态内部类,线程安全

Lazy initialization holder class 模式,这个模式综合了java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全

public class Singleton {
    /**
     * 私有化构造方法
     */
    private Singleton (){}

    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
     * 而且只有被调用到才会装载,从而实现了延迟加载
     */
    private final static class SingletonHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static final Singleton INSTANCE = new Singleton();
    }

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

使用JVM本身机制保证了线程安全问题:SingletonHolder是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖JDK版本.

5.枚举,线程安全

单元素的枚举类型是实现Singleton的最佳方法

  • java的枚举类型实质上是功能齐全的类,可以有自己的属性和方法
  • 枚举的思想是通过公有的静态final域为每个枚举常量导出实例的类
  • 从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举
public enum Singleton {
    /**
     * 定义一个枚举的元素,它就代表了Singleton的一个实例
     */
    INSTANCE;

    /**
     * 示意方法,单例可以有自己的操作
     */
    public void singletonOperation(){
        //功能处理
    }
}

6.单例模式的本质

控制实例的数目

import java.util.HashMap;
import java.util.Map;

/**
 * 扩展单例模式,控制实例数目为3个,
 * 简单演示,实际使用要考虑线程安全和实例调度问题
 */
public class OneExtend {
    /**
     * 定义一个缺省的key值的前缀
     */
    private final static String DEFAULT_PREKEY = "Cache";
    /**
     * 缓存实例的容器
     */
    private static Map<String,OneExtend> map = new HashMap<String,OneExtend>();
    /**
     * 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始
     */
    private static int num = 1;
    /**
     * 定义控制实例的最大数目
     */
    private final static int NUM_MAX = 3;
    private OneExtend(){}
    public static OneExtend getInstance(){
        String key = DEFAULT_PREKEY+num;
        OneExtend oneExtend = map.get(key);
        if(oneExtend==null){
            oneExtend = new OneExtend();
            map.put(key, oneExtend);
        }
        //把当前实例的序号加1
        num++;
        if(num > NUM_MAX){
            //如果实例的序号已经达到最大数目了,那就重复从1开始获取
            num = 1;
        }
        return oneExtend;
    }
}

results matching ""

    No results matching ""