java 设计模式之 -- 单例模式

1,558 阅读4分钟

Java的Singleton设计模式的最佳实践与实例

单例模式是GoF设计模式其中的一种并且属于创造的设计模式目录。

定义上,这似乎是一种非常简单的设计模式,但从实现的角度来说涉及到非常多的方面。 单例模式的实现在开发者中一直是个很有争议的话题。

这里我们将学习单例模式原则,不同的单例实现方式和最佳实践。

单例模式

单例模式限制类的实例和确保java类在java虚拟机中只有一个实例的存在。

单例类必须提供一个全局的访问来获取类的实例。

单例模式用来日志,驱动对象,缓存和线程池。

单例设计模式也用在其他设计模式,例如抽象工厂,建造者,原型,门面等设计模式。

单例模式还用在核心java中,例如java.lang.Runtime, java.awt.Desktop

java单例模式

为了实现Singleton模式,我们有不同的方法,但它们都有以下共同的概念。

  • 私有构造方法限制从其他类初始化类的实例。
  • 私有静态变量与该类的实例相同。
  • 公有静态方法返回类的实例,这是提供给外部访问的全局访问点来获取单例类的实例。在以下的章节,我们将学习单例模式的不同实现方法。
实现的类型
  • 饿汉模式
  • 静态初始化
  • 懒加载
  • 线程安全的单例
  • Bill Pugh单例实现(比尔·普格单例实现)
  • 枚举单例

饿汉模式

饿汉模式就是当类加载时就创建该类的实例,这是创建单例类最容易的方法但是有个一弊端是创建了该实例但是客户端程序可能不使用这个实例。

以下是静态初始化单例类的实现。

public class EagerInitializedSingleton {

    private static EagerInitializedSingleton instance  = new EagerInitializedSingleton();

    private EagerInitializedSingleton(){

    }

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

在大多数的情景,单例类的创建是为了如文件系统,数据库连接等资源的管理。我们应该避免过早的创建类的实例化,除非直到客户端调用getInstance()方法。 这种方法也没有提供异常处理的任何选项。

静态初始化块

静块初始化实现类似于饿汉模式初始化,但类的实例在静态代码块中创建并对异常进行处理。

public class StaticBlockSingleton {
    private static StaticBlockSingleton instance ;

    static {
        try{
            instance = new StaticBlockSingleton();
        }catch (Exception e){
            throw new RuntimeException("静态代码块中实例化失败");
        }

    }

    public static StaticBlockSingleton getInstance(){
        return instance;
    }

}

饿汉模式和静态初始化两种实现都是在实例在被使用之前就已经创建了,这不是最佳实践。 以下章节中,我们将会学习如何创建支持懒加载的单例类。

懒加载

相比之前的两种方法,懒加载就是当需要的时候再来创建该类的实例,而不是一开始就把实例创建好了。

public class LazyInitializedSingleton {
    private static LazyInitializedSingleton instance = null;

    private LazyInitializedSingleton(){

    }

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return  instance;
    }

}

懒加载的实现在单线程环境中可以正常使用,但是在多线程环境中会引发一些问题。这将会破坏单例模式和线程会获取不同的单例对象(不能保证线程安全) 。下一章节中,我们将会通过不同方法来实现线程安全的单例类;

线程安全的单例

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance = null;

    private ThreadSafeSingleton(){

    }

    //同步关键字synchronized
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return  instance;
    }

}

上述实现能正常运行,并提供了线程安全,但它降低了程序的性能,因为synchronized关键字修饰的是getInstance()整个方法。 但我们需要它仅用于谁可能创建单独的实例的第一个线程数(阅读:Java同步)。为了避免这种每一次的额外开销,使用双检查锁定原理。在这种方法中,synchronized块使用if条件里面加上一个额外的检查,以确保只创建一个单独的类的实例。

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    //synchronized同步块,控制更细的粒度
    if(instance == null){//此层的控制允许第一个线程进入访问,避免以上情况(同步方法)每次的等待开销。
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){//此处校验单例是否已经被创建,确实只有一个实例的存在。
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Bill Pugh单例

比尔普格想出了一个不同的方法来创建一个使用静态内部辅助类的Singleton类。

public class BillPughSingleton {

    private static class SingleTonHelpClass{
        private static  final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance(){
        return SingleTonHelpClass.INSTANCE;
    }

}

当单例类被加载,SingletonHelper内部类没有加载到内存中,只有当调用getInstance()方法时,该类被载入并创建Singleton类的实例。这种单例类因为它不要求使用同步的方法,容易理解和实现。

枚举单例

这种方法简单,便捷。

public enum  EnumSingleton {
    INSTANCE;

    public static void doSomething(){
        //do something
    }
}