1. 单例模式介绍

单例模式提供了一种创建对象的最佳方式。
由某类负责创建自己的对象,并提供该对象的访问方式,以确保该对象是该类唯一的实例。

(1) 适用情况:

存在一个全局使用的类被频繁的创建和销毁时,可以考虑使用单例模式。

(2) 优点:

在堆中有且仅有一个实例,避免了频繁创建销毁以及保存的内存开销。

(3) 缺点:

没有接口,不能继承。
它在类的内部创建了自己的对象,这与单一职责原则冲突,它本来不应该关心外部是怎么实例化它的。

2. 单例模式实例

(1) 懒汉式(线程不安全)

先来看一种最简单的单例模式:

public class Singleton {
    private static Singleton instance;

    // 私有构造方法
    private Singleton() {
    }

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

为什么叫懒汉,因为它不急着创建实例,而是等到判定为空的时候才去创建

它最大的问题就是线程不安全,因为可能会有多个线程判定instance == null,从而创建多个实例,严格意义上说,它并不能算单例模式。

(2) 懒汉式(线程安全)

上边的实现是线程不安全的,那给getSingleton方法增加synchronized不就行了吗:

public class Singleton {
    private static Singleton instance;

    // 私有构造方法
    private Singleton() {
    }

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

这样做确实是可以保证线程安全的,但是加锁会影响效率
其实除了第一次需要创建之外,后续的加锁就没有意义了

(3) 饿汉式(常用)

饿汉式指的是在类加载的时候,就直接创建了实例:

public class Singleton {
    private static Singleton instance = new Singleton();

    // 私有构造方法
    private Singleton() {
    }

    public static Singleton getSingleton() {
        return instance;
    }
}

这是比较常用的方式,也可以保证线程安全
但是在类加载的时候就实例化的话,万一它很消耗资源,就有点浪费内存

(4) 双重校验锁(面试专属)

使用双重校验锁的方式能同时保证线程安全和高性能:

public class Singleton {
    // 注意这里的volatile
    private volatile static Singleton instance;

    // 私有构造方法
    private Singleton() {
    }

    public static Singleton getSingleton() {
        // 第一次判断是否为空,为空才创建,否则直接返回
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断是否为空,防止已被其他线程实例化之后重复实例化
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这种方式中判断了两次instance == null:
第一次判断是否为空,为空才创建,否则直接返回
第二次判断是否为空,防止已被其他线程实例化之后重复实例化
这里的关键是在声明instance变量的时候,增加了volatile关键字

volatile有两个作用:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。但如果对该变量执行的是非原子操作,它不能保证线程安全
  2. 禁止指令重排序

(5) 静态内部类(优化的饿汉式)

上边说过饿汉式在类加载的时候,就直接创建了实例,比较浪费内存
可以使用静态内部类的方式,当显示调用getInstance方法时,才会去装载SingletonHolder类,从而实例化instance

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 私有构造方法
    private Singleton() {
    }

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

(6) 枚举(用的少,大神推荐)

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

3. 一些思考

最近工作中遇到了一个问题,详细的暂时不展开说了
大致就是某一个系统在长时间运行后,出现了元空间Metaspace的OOM错误
通过阿里开源的内存监控工具Arthas发现了端倪:某一个类加载器被创建了几万次!
阅读代码发现,是因为在我们的代码里重复创建一个类的对象,而每次创建该对象时,都会创建一个类加载器,最终使得元空间OOM了。

后来,我们使用了单例模式,不再重复创建该对象,就完美地解决了这个问题。

参考引用

单例模式:https://www.runoob.com/design...


Zealf
13 声望1 粉丝