单例模式估计是咱们碰到最多也是最简单的一种设计模式了(也是面试中经常会遇到的面试题)。单例模式保证一个类只有一个实例,比如咱们在Android应用中登入成功之后保存用户信息就会优先考虑单例模式。
单例模式有六种常规的写法:饿汉式、懒汉式(线程不安全)、懒汉式(线程安全)、DCL双重校验模式、静态内部类、枚举。
一、饿汉式
饿汉式,有两种不同的写法:静态常量、静态代码块。
饿汉式-静态常量
public class Singleton {
private Singleton() {
}
/**
* 静态常量
*/
private final static Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
}
饿汉式-静态代码块
public class Singleton {
private Singleton() {
}
/**
* 静态代码块
*/
private static Singleton INSTANCE;
static {
INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return INSTANCE;
}
}
饿汉式实现单例有两种方式:静态常量、静态代码块。两种写法不管是在性能上还是在效果上都是一样的,都是在类装载的时候完成实例化。类装载的时候JVM保证了线程安全。
特点:没有懒加载(类装载的时候就初始化)、线程安全。不推荐使用
二、懒汉式(线程不安全)
public class Singleton {
private Singleton() {
}
private static Singleton INSTANCE;
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
这种写法在单线程的程序中没啥问题,在第一次调用getInstance()方法的时候创建实例(懒加载)。但是在多线程的情况下多个线程同时调用getInstance()函数的时候在同一时刻这些线程都通过了if (INSTANCE == null)的判断,那就有可能重复创建了多个实例了。
特点:懒加载(getInstance的时候才初始化)、线程不安全。不推荐使用
三、懒汉式(线程安全)
public class Singleton {
private Singleton() {
}
private static Singleton INSTANCE;
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
这种写法给getInstance()方法添加了synchronized,告诉JVM这是个同步方法。当多个线程同时调用getInstance()方法时,同一时刻只能有一个线程通过getInstance()方法,其他的线程只能等待。所以这种方式效率相对来说比较低。
特点:懒加载(getInstance的时候才初始化)、线程安全、效率低(多个线程同时getInstance的时候会有等待的情况)。不推荐使用
懒汉式里面的懒字是懒加载的懒。为了和饿汉式对应有的地方也把懒汉式叫做饱汉式。
四、DCL双重校验模式(推荐)
public class Singleton {
private Singleton() {
}
private volatile static Singleton INSTANCE;
public static Singleton getInstance() {
if (INSTANCE == null) { //第一次校验
synchronized (Singleton.class) {
if (INSTANCE == null) { //第二次校验
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
这种写法在懒汉式(线程安全)方式之上做了进一步的提高。进行了两次if (INSTANCE == null)检查:第一次判断是为了避免不必要的同步从而提高效率、第二次判断之前添加了synchronized同步代码块保证了同一时刻只有一个线程能进来创建实例。
特点:懒加载、线程安全、效率高。推荐使用
五、静态内部类(推荐)
public class Singleton {
private Singleton() {
}
/**
* 静态内部类
*/
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
这种实现方式在Singleton类被装载时并不会实例化,只有在第一次调用getInstance()方法的时候才会装载内部类SingletonInstance从而完成Singleton的初始化,JVM保证了在类进行初始化时,别的线程是无法进入的。所以静态内部类实现单例的方法是线程安全的。而且还起到了懒加载的作用。
特点:懒加载、线程安全。推荐使用
六、枚举(推荐)
枚举可以有自己的属性,也可以有自己的方法。
第一种写法(代码简洁,强烈推荐):
这里我假设mUserName来我要在单例里面保存的一个属性,之后我们可以通过SingletonEnum.INSTANCE.getUserName();来操作单例里面的属性。
public enum SingletonEnum {
INSTANCE;
private String mUserName;
public String getUserName() {
return mUserName;
}
public void setUserName(String userName) {
mUserName = userName;
}
}
第二种写法,我们额外的多一个Singleton类,Singleton类里面放我们单例要保存的一些信息。调用的时候通过SingletonEnum.INSTANCE.getInstance()获取到Singleton类:
额外的Singleton类
public class Singleton {
}
public enum SingletonEnum {
INSTANCE;
private Singleton instance;
SingletonEnum() {
instance = new Singleton();
}
public Singleton getInstance() {
return instance;
}
}
枚举实例JVM保证了线程安全,并且在任何情况下,它都是一个单例。同时枚举也是在第一次使用的时候初始化,这样也保证了懒加载。
特点:懒加载、线程安全。推荐使用
综上所述,综合效率,安全等因素对于单例模式实现的选择,我们优先推荐枚举(代码简洁),其次静态内部类,最后是DCL双重校验模式。
网友评论