单例模式的最佳实现方式

399 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

谈到设计模式,记忆最为深刻的便是单例模式,单例模式是设计模式中最简单的设计模式之一,属于创建型模式。它出现目的是为了保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。从这点可以看出,单例模式的出现是为了可以保证系统中一个类只有一个实例而且该实例又易于外界访问,从而方便对实例个数的控制并节约系统资源而出现的解决方案。

单例模式的实现方式

单例模式根据其创建对象的时机不同分为饿汉式和懒汉式,懒汉式在实现时又需要考虑线程安全,从而有线程安全和不安全,以及为了进一步提高性能的双重检查实现。最后还有依赖于静态内部类和,枚举实现的单例模式。

1. 饿汉式

饿汉式在实现时,不管用没用到直接创建实例对象,这种方式最大问题就是会浪费内存,如果一个对象在创建后一直未被使用就会占用内存空间。当然也有一定优点,就是在类装载时完成实例化,避免了线程同步问题。

 class Singleton {

    //私有化构造对象
    private Singleton(){

    }
    //静态常量
    private static Singleton singleton = new Singleton();

    //提供静态方法 获取对象
    public static Singleton getInstance(){
        return singleton;
    }
}

总结起来饿汉式实现单例模式就三步:

  • 私有化构造函数
  • 声明单例对象实例
  • 提供静态方法getInstance,获取实例对象

2. 懒汉式

懒汉式,其如字面意思,很懒,在用到时才去创建实例对象。

2.1 懒汉式-线程不安全

class Singleton {

    //静态常量
    private static Singleton singleton;

    //私有化构造对象
    private Singleton(){

    }
    //提供静态方法 获取对象
    public static Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

这种方式能够实现懒加载,但是只能在单线程下使用,多线程环境下,一个线程进入了if(singleton == null)判断语句,还未来得及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例。因此在实际编程时不能使用这种方式。

2.2 懒汉式-线程安全

要想线程安全,一般来说,最简单的就是加锁,使用synchronized关键字。

class Singleton {

    //静态常量
    private static Singleton singleton;

    //私有化构造对象
    private Singleton(){

    }
    //提供静态方法 获取对象
    public static synchronized Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

这种方式解决了线程不安全,但是直接使用synchronized这种重量级锁会使得程序运行效率降低。每个线程在想获得实例对象时都需要进行同步,但其实只需要执行一次实例化就可以,后面想获得实例直接return返回即可。因此,这种方法也不推荐使用。

2.3 双重检查

双重检查,就是进行两次if(singleton == null)检查。

class Singleton {

    //静态常量
    private static Singleton singleton;

    //私有化构造对象
    private Singleton(){

    }
    //提供静态方法 获取对象
    public static   Singleton getInstance(){
        //第一次判断
        if(singleton==null){
            synchronized (Singleton.class){
                //第二次判断
                if(singleton==null){
                    singleton = new Singleton();
                }
            }

        }
        return singleton;
    }
}

这种方式不仅解决了线程安全问题,也进一步提高了程序执行效率。实例化代码只需要执行一次,后面在此访问时,第一次判断if(singleton == null)就可以直接return实例对象。在实际开发中,这种方式是比较推荐的。

3.静态内部类

利用静态内部类方式实现单例模式,Singleton类被装载时不会立即实例化,而是在需要实例时,调用getInstance,Singleton才会被装载,起到了懒加载的效果。另一方面,静态属性只会在第一次加载时初始化,这样JVM就帮我们保证了线程安全。

class Singleton{

    private Singleton(){}

    //静态内部类
    private static class SingletonInstance{
        private static final Singleton Instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonInstance.Instance;
    }

}

这种方法线程安全、延迟加载、效率高,在实际开发中推荐使用。

4. 枚举

enum Singleton{
    //实际上在第一行写枚举类实例的时候,默认是调用了构造器的,而且构造器是私有的
    INSTANCE;
    public void method(){
        System.out.println("获得实例对象");
    }
}

这种方式不仅能避免线程同步问题,还能防止反序列化重新创建新的对象。

综合比较来说,枚举是最佳的,其次是静态内部类和双重检查。