单例模式的八种写法、单例和并发的关系

发布时间:2024年01月13日

1.单例模式的作用

为什么需要单例?

  • 节省内存和计算
  • 保证结果正确
  • 方便管理

2.单例模式的适用场景

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

3.饿汉式

静态常量(可用)

/**
 * 饿汉式(静态常量)(可用)
 */
public class Singleton1 {

    // 由于加了static关键字,根据JVM的规定,在类加载的时候就会完成INSTANCE的实例化,这样就避免了线程同步问题
    private final static Singleton1 INSTANCE = new Singleton1();

    // 构造函数是私有的
    private Singleton1() {

    }

    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

静态代码块(可用)

/**
 * 饿汉式(静态代码块)(可用)
 */
public class Singleton2 {

    private final static Singleton2 INSTANCE;

    // 与上一种写法类似,由JVM保证了线程安全
    static {
        INSTANCE = new Singleton2();
    }

    // 构造函数是私有的
    private Singleton2() {

    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

4.懒汉式

线程不安全(不可用)

/**
 * 懒汉式(线程不安全)(不可用)
 */
public class Singleton3 {

    private static Singleton3 instance;

    // 构造函数是私有的
    private Singleton3() {

    }

    public static Singleton3 getInstance() {
        // 这种写法是线程不安全的,不可用
        if (instance == null) {
            instance = new Singleton3();
        }
        return instance;
    }
}

同步方法(线程安全,但不推荐用)

/**
 * 懒汉式(线程安全)(不推荐用)
 */
public class Singleton4 {

    private static Singleton4 instance;

    // 构造函数是私有的
    private Singleton4() {

    }

    // 这种写法虽然是线程安全的,但是效率太低,不推荐用
    public synchronized static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
}

同步代码块(线程不安全,不可用)

/**
 * 懒汉式(线程不安全)(不可用)
 */
public class Singleton5 {

    private static Singleton5 instance;

    // 构造函数是私有的
    private Singleton5() {

    }

    public static Singleton5 getInstance() {
        // 这种写法并不是线程安全的,不可用
        if (instance == null) {
            synchronized (Singleton5.class) {
                instance = new Singleton5();
            }
        }
        return instance;
    }
}

双重检查 + volatile(推荐用)

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

/**
 * 双重检查 + volatile(推荐用)
 */
public class Singleton6 {

    // volatile防止重排序
    private volatile static Singleton6 instance;

    // 构造函数是私有的
    private Singleton6() {

    }

    public static Singleton6 getInstance() {
        // 双重检查保证线程安全
        if (instance == null) {
            synchronized (Singleton6.class) {
                if (instance == null) {
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
}

为什么要用 volatile?

新建对象 rs = new Resource() 实际上有 3 个步骤:

  • construct empty resource()
  • call constructor
  • assign to rs

如下图所示,重排序会带来NPE问题(NullPointerException, 空指针异常),而使用 volatile 可以防止重排序。

在这里插入图片描述

静态内部类(推荐用)

/**
 * 静态内部类(线程安全,懒加载)(推荐用)
 */
public class Singleton7 {

    // 构造函数是私有的
    private Singleton7() {

    }

    // 由JVM的规定可知,这种写法同时满足了线程安全和懒加载两个优点
    private static class SingletonInstance {
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

枚举(推荐用)

单例模式的书写:

/**
 * 枚举(线程安全,懒加载)(推荐用)
 */
public enum Singleton8 {
    INSTANCE;

    public void whatever() {

    }
}

单例的使用:

Singleton8.INSTANCE.whatever();

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

Joshua Bloch 大神在《Effective Java》中明确表达过的观点:使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton 的最佳方法。

  • 写法简单
  • 线程安全有保障
  • 懒加载
  • 避免反序列化破坏单例
文章来源:https://blog.csdn.net/qq_42815188/article/details/135431566
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。