单例模式的8种写法

JVM内存结构 VS Java内存模型 VS Java对象模型:https://blog.csdn.net/qq_40794973/article/details/103960056 

JMM应用实例单例模式8种写法、单例和并发的关系(真实面试超高频考点)


1 单例模式的作用和适用场景

单例模式的作用

  1. 为什么需要单例:节省内存和计算、保证结果正确、方便管理
private Resource rs = new Resource();
public Resource getExpensiveResource(){
    return rs;
}
public Resource(){
    field1 = //some CPU heavy logic
    field2 = //some value from DB
    field3 = //etc.
}

 

单例模式适用场景 

  1. 无状态的工具类:比如日志工具类,不管是在哪里使用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象即可。
  2. 全局信息类:比如我们在一个类上记录网站的访问次数,我们不希望有的访问被记录在对象A上,有的却记录在对象B上,这时候我们就让这个类成为单例。

1 饿汉式(静态常量)[可用] 

/**
 * 饿汉式(静态常量)(可用)
 */
public class Singleton {
    private final static Singleton instance = new Singleton();
    // 构造函数私有化
    private Singleton() {
        //....
    }
    public static Singleton getInstance() {
        return instance;
    }
}

2 饿汉式(静态代码块)[可用]

/**
 * 饿汉式(静态代码块)(可用)
 */
public class Singleton {
    private final static Singleton instance;
    static {
        instance = new Singleton();
    }
    // 私有构造函数
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        return instance;
    }
}

注意!!! 

/**
 * 写法不当容易产生NPE
 */
public class Singleton {
    static {
        instance = new Singleton();
    }
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
    }
}

3 懒汉式(线程不安全)[不可用]

/**
 * 懒汉式(线程不安全)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

4 懒汉式(线程安全,同步方法)[不推荐用]

/**
 * 懒汉式(线程安全)(不推荐)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    //多个线程无法并行执行,不能及时的响应
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

5 懒汉式(线程不安全,同步代码块)[不可用]

/**
 * 懒汉式(线程不安全)(不推荐)
 */
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        //并没有解决线程安全问题
        if (instance == null) {
            //这里虽然是同步的,但是还是会多次运行
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

6 双重检查[推荐用]

优点:线程安全;延迟加载;效率较高。


为什么要double-check

  1. 线程安全
  2. 单check行不行?//可 第四种
  3. 性能问题

为什么要用volatile

  1. 新建对象实际上有3个步骤
  2. 重排序会带来NPE
  3. 防止重排序

单例模式的8种写法_第1张图片

/**
 * 双重检查(推荐面试使用)
 */
public class Singleton {
    //注意volatile
    //1.可见性
    //2.防止重排序
    private volatile static Singleton instance;
    private Singleton() {
        //...
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                //虽然可能进来多个,但是我再做一次检查即可避免多次创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

不加volatile产生错误分析

//非线程安全
public class Singleton {
    // 单例对象
    private static Singleton instance = null;
    // 私有构造函数
    private Singleton() {
    }
    // 1、memory = allocate() 分配对象的内存空间
    // 2、ctorInstance() 初始化对象
    // 3、instance = memory 设置instance指向刚分配的内存
    // JVM和cpu优化,发生了指令重排
    // 1、memory = allocate() 分配对象的内存空间
    // 3、instance = memory 设置instance指向刚分配的内存
    // 2、ctorInstance() 初始化对象
    
    // 静态的工厂方法
    public static Singleton getInstance() {
        if (instance == null) { // 线程2:线程1已经执行了重排序后的前两个指令,instance!=mull 了但是单例对象还没有初始化完成,这个时候返回的是未初始化完成的对象
            synchronized (Singleton.class) { // 同步锁
                if (instance == null) {
                    instance = new Singleton(); // 线程1:在执行指令重排后的3步
                }
            }
        }
        return instance;
    }
}

7 静态内部类[推荐用]

/**
 * 静态内部类方式,可用
 */
public class Singleton {
    private Singleton() {
        //...
    }
    //JVM类加载的性质,保证了即便多个线程同时访问这个SingletonInstance,也不会创建多个实例
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

懒汉,调用这个方法才会初始化 ;加载一个类时,其内部类不会同时被加载

public class Singleton {
    static {
        System.out.println("Singleton被初始化啦...");
    }
    private Singleton() {
        //...
    }
    private static class SingletonInstance {
        static {
            System.out.println("Singleton里面的静态内部类被初始化啦...");
        }
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
    public static void main(String[] args) {
        //Singleton.getInstance();//只有调用这个方法,Singleton里面的静态内部类才会被初始化
    }
}

8 枚举[推荐用]

/**
 * 枚举单例(推荐生产实践中使用)
 */
public enum Singleton {
    //INSTANCE
    INSTANCE;
    /**
     * 类中的方法
     */
    public void whatever() {
        System.out.println("whatever");
    }
}
public class Main {
    public static void main(String[] args) {
        Singleton.INSTANCE.whatever();
    }
}

另一种写法

public class SingletonExample {
    // 私有构造函数
    private SingletonExample() {
    }
    public static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
    private enum Singleton {
        INSTANCE;
        private SingletonExample singleton;
        // JVM保证这个方法绝对只调用一次
        Singleton() {
            singleton = new SingletonExample();
        }
        public SingletonExample getInstance() {
            return singleton;
        }
    }
}

9 不同写法对比

  • 饿汉:简单,但是没有 lazy loading
  • 懒汉:有线程安全问题
  • 静态内部类:可用
  • 双重检查:面试用
  • 枚举:最好

9.1 用哪种单例的实现方案最好?

  • Joshua Bloch大神在《Effective java》中明确表达过的观点:“使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。”
  • 写法简单
  • 线程安全有保障
  • 避免反序列化破坏单例

9.2 各种写法的适用场合

  • 最好的方法是利用枚举,因为还可以防止反序列化重新创建新的对象
  • 非线程同步的方法不能使用
  • 如果程序一开始要加载的资源太多,那么就应该使用懒加载
  • 饿汉式如果是对象的创建需要配置文件就不适用
  • 懒加载虽然好,但是静态內部类这种方式会引入编程复杂性

9.3 其他问题 

饿汉式的缺点?

  • 优点:写法简单,线程安全
  • 缺点:程序启动就加载,不需要这个实例也加载了,造成资源的浪费

懒汉式的缺点?

  • 优点:解决了前期加载浪费的问题,只在需要的时候才加载进来
  • 缺点:写法相对比较复杂,不注意容易写成线程不安全的情况

为什么要用 double-check?不用就不安全吗?

  1. 为什么单重锁是线程池不安全的
  2. 逐渐演变升级到双重锁代码 //懒加载 线程安全
  3. synchronized加方法上虽然安全,但是效率底响应慢

为什么双重检査模式要用volatile?

 


应该如何选择,用哪种单例的实现方案最好?

 

//饿汉式
//java.lang.Runtime
public class Runtime {
	private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    //构造方法私有化
    private Runtime() {}

 

你可能感兴趣的:(多线程)