剑指offer——面试题2:实现Singleton模式

1. 懒汉式:

public class Singleton {
    //1. 本类内部创建对象实例
    private static Singleton instance = null;

    //2. 构造方法私有化,不允许外部new
    private Singleton(){}

    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式单例模式在调用时先判断实例是否存在,如果存在就直接使用,如果不存在就创建一个实例。(以时间换空间)
不加同步的懒汉式是线程不安全的,因为可能同时两个线程都判断到对象不存在,然后都new一个对象。
因此,需要使用双重检查加锁的方法。
一个线程想要访问对象,必须先获取到对象的监视器(monitor),synchronized最大的特征就是在同一时刻只有一个线程能够获得对象的监视器。
用volatile修饰对象实例用来限制指令重排序。Java类初始化过程是非原子操作,会经历三个阶段:
(1)分配内存给这个对象
(2)初始化对象
(3)将引用指向刚分配的内存空间
其中(2)和(3)可能会指令重排序,也就是说执行顺序可能是123也可能是132。所以如果执行顺序是132,那么当线程执行完3,此时对象已经不为空,但是并没有初始化。此时另一个线程抢占了CPU,那么执行getInstance()方法时就会直接返回instance(非 null 但却没有初始化),后续使用就会出错了。

public class Singleton {
    //1. 本类内部创建对象实例,加volatile禁止指令重排序
    private static volatile Singleton instance = null;

    //2. 构造方法私有化,不允许外部new
    private Singleton(){}

    //3. 提供一个公有的静态同步方法,返回实例对象
    public static synchronized Singleton getInstance() {
        //4.第一次判空检查,主要是为了效率,当instance为null的时候,才进入synchronized代码段
        if (instance == null) {    
            //5. new之前加锁
            synchronized (Singleton.class) {
                //6. 第二次判空检查,主要是为了防止可能出现多个实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 饿汉式:

public class Singleton {
    //1. 创建实例
    private static Singleton instance = new Singleton();

    //2. 构造方法私有化,不允许外部new 
    private Singleton() {}

    //3. 提供一个公有的静态同步方法,返回实例对象
    public static Singleton getInstance() {
        return singleton;
    }
}

饿汉式在类加载的时候就会创建实例,在调用时不需要再判断了。(以空间换时间)

3. 使用静态内部类实现单例模式:

public class Singleton {
    //1. 静态内部类,只会在虚拟机装载类的时候初始化一次
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    //2. 构造方法私有化,不允许外部new
    private Singleton(){}

    //3. 提供一个公有的静态同步方法,返回实例对象
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

当getInstance方法第一次被调用的时候,它会返回SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

参考&鸣谢:

-《java 单例模式的几种实现方式》
  https://blog.csdn.net/fd2025/article/details/79711198
-《单例模式中的懒汉式以及线程安全性问题》
  https://www.cnblogs.com/sunnyDream/p/8011186.html

你可能感兴趣的:(剑指offer——面试题2:实现Singleton模式)