单例模式实现的几种方式

方式一:饿汉模式

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

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

在ClassLoader加载类时,实例化出一个对象,其后使用时返回该对象。

  • 优点:线程安全,代码简洁
  • 缺点:实例长期被静态成员持有,从类加载开始就一直常驻内存

方式二:懒汉模式

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

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

在实例被使用时初始化,采用懒加载的方式,所以俗称懒汉模式

  • 优点:在使用时加载,提高资源利用率和程序的运行效率
  • 缺点:多线程场景下线程不安全

方式三:线程安全模式

饿汉模式资源利用率低,懒汉模式线程不安全,于是就有了线程安全的懒汉模式
这种模式有几种写法

代码1

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

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

代码2

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

代码3

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

代码1和代码2,本质上是一样的,现货区类的class对象同步锁,然后判断对象是否为空,为空则实例化对象,随后返回对象,这两种模式是可以实现线程安全的,缺点是,每次调用getInstance()获取对象,都要活动类的class对象的同步对象锁;至于代码3,是不能实习线程安全的,因为在判空阶段没有使用同步代码块,对象还是有可能会重复创建。

综合代码1、2、3,得出以下实现方式:

代码4:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {//第一次校验,如果不对象为空,直接返回,不必获取同步对象锁
            synchronized (Singleton.class) {
                if (instance == null) {//第二次校验,获取同步对象锁之后再去检验
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在获取类的class同步对象前后,各做一次判断,有效防止对象多次创建。这种方式仍然是不稳定的,jdk1.5之后引入了volatile关键字。

代码5:双重校验锁式

public class Singleton {

    private static volatile Singleton instance;//这里使用了volatile关键字

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {//第一次校验,如果不对象为空,直接返回,不必获取同步对象锁
            synchronized (Singleton.class) {
                if (instance == null) {//第二次校验,获取同步对象锁之后再去检验
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。
这种方式一般称为双重校验锁式,也是我最喜欢使用的一种方式。

  • 优点:线程安全,实现懒加载,资源利用利用率和运行效率较高
  • 缺点:代码量稍微大了些,jdk1.5之前不稳定

方式四:静态内部类式

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {

        return InnerStaticClass.singleton;
    }

    private static class InnerStaticClass {

        private static Singleton singleton = new Singleton();
    }
}

这里先补充下类加载的相关知识,内部类跟外部类不是同时加载的,是在内部类第一次被使用时加载
这种方式和饿汉模式一样是利用了Java的类加载器,保证了实例唯一,同事有保证了又保留了懒汉模式的懒加载特性。

  • 优点:线程安全,实现懒加载,资源利用利用率和运行效率较高
  • 缺点:多写了一个类

特殊:枚举实现单例

public enum Singleton {
    Instance;//只有一个成员

    public  void set(){

    }
}

当枚举只有只有一个成员时,这个成员就是它的唯一实例,这样使用

 Singleton.Instance.set();

当然,写Android程序的时候,我是绝对不会这样用的,Android的其他地方也尽量不用枚举。

你可能感兴趣的:(单例模式实现的几种方式)