单例模式是java中最简单的设计模式之一。单例模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要反复实例化该类的对象。

注意:

  1. 单例类智能有一个实例
  2. 单例类必须自己创建自己的唯一实例
  3. 单例类必须能给所有其他对象提供自己的唯一实例

一个全局使用的类频繁地创建与销毁,会浪费许多系统资源。当你想控制实例数目,节省系统资源的时候,就可以使用单例模式。

单例模式的实现方式

1. 饿汉式

创建一个Person类。

public class Person{
    // 创建一个自己的私有静态对象
    private static Person instance = new Person();
    // 构造函数必须为private,保证该类不会在外部被实例化
    private Person(){
    }
    // 创建getInstance静态方法,其他对象通过此方法获取唯一实例
    public static Person getInstance(){
        return instance;
    }
}

通过调用Person p = Person.getInstance();即可获取唯一Person实例:

这种饿汉式单例模式比较常用,它基于classloader机制避免了多线程同步问题,没有加锁,执行效率会提高。缺点是instance在类装载时就实例化,浪费内存。

2. 懒汉式

public class Person {
    // 定义自己的私有静态实例,保证只有一个实例
    private static Person instance;
    // 构造函数必须为private,保证该类不会在外部被实例化
    private Person() {
    }
    // 创建getInstance静态方法,其他对象通过此方法获取唯一实例
    public static Person getInstance() {
        // 如果该实例为null,则先实例化,否则直接返回该实例
        if (instance == null) {
            return new Person();
        }
        return instance;
    }
}

这种方式是最基本的实现方式,可以在使用时根据情况进行实例化,但最大的问题就是不支持多线程,因为没有加锁,所以严格意义它上并不算单例模式。

3. 懒汉式,线程安全

public class Person {
    // 定义自己的私有静态实例,保证只有一个实例
    private static Person instance;
    // 构造函数必须为private,保证该类不会在外部被实例化
    private Person() {
    }
    // 创建getInstance静态方法,其他对象通过此方法获取唯一实例
    public static synchronized Person getInstance() {
        // 如果该实例为null,则先实例化,否则直接返回该实例
        if (instance == null) {
            return new Person();
        }
        return instance;
    }
}

这种方式具备很好的懒加载,并且能够在多线程中很好地工作,但是由于加锁才能保证单例,然而99%情况下都不需要同步,所以效率很低。

4. 双检锁模式

public class Person {
    // 定义自己的私有静态实例,保证只有一个实例
    private volatile static Person instance;
    // 构造函数必须为private,保证该类不会在外部被实例化
    private Person() {
    }
    // 创建getInstance静态方法,其他对象通过此方法获取唯一实例
    public static Person getInstance() {
        // 如果该实例为null,则先实例化,否则直接返回该实例
        synchronized (Person.class) {
            if (instance == null) {
                return new Person();
            }
        }
        return instance;
    }
}

这种方式线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。这种方式进行了两次判断,第一次是为了避免不必要的实例,第二次是为了进行同步,避免多线程问题。由于对象的创建在JVM中可能会 重排序,在多线程访问下存在风险,使用volatile修饰实例变量有效,可以解决该问题。

5. 静态内部类单例模式(推荐)

public class Person {
    // 构造函数必须为private,保证该类不会在外部被实例化
    private Person() {
    }
    // 定义静态内部类
    private static class Inner {
        private static final Person INSTANCE = new Person();
    }
    // 创建getInstance静态方法,其他对象通过此方法获取唯一实例
    public static final Person getInstance() {
        return Inner.INSTANCE;
    }
}

这种方式同样利用了classloader机制来保证初始化instance实例时只有一个线程。只有第一次调用getInstance()方法时才显式加载 Inner类,从而实例化 instance,保证对象的唯一性。可以达到与双检锁方式一样的效果。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。

6. 枚举

public enum Person {
    INSTANCE;
}

默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。枚举单例模式在《Effective Java》中推荐的单例模式之一。它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

注意:一般情况下,不建议使用懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类单例模式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

注意

单例模式作为一种创建型模式,都会新建一个实例,那么一个重要的问题就是反序列化。当实例被写入到文件反序列化为实例时,会创建新的对象,这就破坏了单例模式的规则。我们需要重写readResolve()方法,以让实例唯一:

private Object readResolve() throws ObjectStreamException{
        return instance;
}
文章目录