单例模式

255 阅读3分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

单例模式

指一个类只有一个实例,且该类能自行创建这个实例的一种模式

特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

优点

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  • 避免频繁的创建销毁对象,可以提高性能;
  • 可以避免对资源的多重占用。
  • 单例模式设置全局访问点,可以优化和共享资源的访问。

缺点

  • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

使用场景:

  • 要求生产唯一序列号。
  • WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

手撸代码

1、饿汉式

在加载的时候就加载了,不管是什么时候用到,这样保证了线程的安全,但是如果长时间没用到则浪费了内存空间
public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}
    public static Singleton get() {
        return singleton;
    }
}

2、懒汉式

懒汉式单例是延迟加载,只有需要的时候才会实例化对象再使用
// 懒汉式
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton get() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

这种写法在多线程环境是线程不安全的,当一个线程通过if (singleton1 == null)时,另一个线程也同时通过了这个判断,这样就产生了多个实例。

3、双重校验锁

属于懒汉式的一种,在懒汉式的基础上加了synchronized关键字
// 双重校验锁
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton get() {
        if (singleton == null) {
            synchronized(Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

这样先判断一次,再加锁进行第二次判断,是线程安全的;

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

4、内部静态类

public class Singleton {
    private static class SingletonHolder {
        private static Singleton singleton = new Singleton();
    }
    private Singleton() {}
    public static Singleton get() {
        return SingletonHolder.singleton;
    }
}

在项目启动初始化时,虚拟机只加载了Singleton类,内部的SingletonHolder并没有加载,只有第一次加载get方法时,虚拟机才加载SingletonHolder并初始化singleton;保证了对象的唯一性。

5、枚举

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

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象

通过枚举类,他能自动避免序列化/反序列化攻击,以及反射攻击(枚举类不能通过反射生成)。