【设计模式】 单例模式:别忘了还可以用枚举
概念
单例模式就是为了确保一个类当中只有一个实例,并且提供一个全局的公有访问点。
通俗的说,当我们需要用到某个对象的实例的时候,我们无需进行其它多余操作,而是直接通过某个接口获取到它的实例,并且这个实例在整个系统中保证唯一。
要求
这就需要我们满足两点要求:
- 生成的类的实例是唯一的,也就是说生成对象实例的代码只能够运行一次
- 生成实例的方法必须是静态方法。因为非静态方法的必须通过实例进行调用,如果已经有了实例,我们还需要生成实例的方法干什么呢,就成了先有鸡还是先有蛋的问题了。
实现方式
- 饿汉式
- 懒汉式
- 双重检查
- 静态内部类
- 枚举
饿汉式
不管需不需要,我都要创建一个实例。
1. 使用静态常量实现
1) 构造器私有化(防止new)
2) 在类的内部创建对象
3) 向外暴露一个静态的公共方法:getInstance
//饿汉式(静态变量)
class Singleton{
//1. 构造器私有化, 防止外部能new
private Singleton() {}
//2.本类内部创建对象实例
private static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优点:这种写法比较简单,在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2. 使用静态代码块实现
1) 构造器私有化(防止new)
2) 在类的内部使用静态代码块创建对象
3) 向外暴露一个静态的公共方法:getInstance
//饿汉式(静态代码块)
class Singleton{
//1. 构造器私有化, 防止外部能new
private Singleton() {}
//2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点:这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
懒汉式
用的时候才创建,所以叫懒汉式。
1) 构造器私有化(防止new)
2) 在类的内部使用静态代码块创建对象
3) 向外暴露一个静态的公共方法:getInstance
//懒汉式
class Singleton {
//1.构造方法私有化,防止外部调用
private Singleton() {}
//2.声明该静态对象
private static Singleton instance;
//3.提供一个静态的公有方法,当使用到该方法时,才去创建instance
public static Singleton synchronized getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:起到了Lazy Loading 的效果。
缺点:效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return 就行了。方法进行同步效率太低。
结论:在实际开发中,不推荐使用这种方式.
双重检查(较推荐)
//双重检查
class Singleton {
//1. 私有化构造方法
private Singleton() {}
//2. 声明静态对象
private static Singleton instance;
//提供一个静态的公有方法,加入双重检查代码,同时解决线程安全,懒加载和效率问题
public static synchronized Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
}
return instance;
}
1) Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
2) 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return 实例化对象,也避免的反复进行方法同步.
3) 线程安全;延迟加载;效率较高
4) 结论:在实际开发中,较为推荐使用这种单例设计模式
静态内部类模式(推荐)
//静态内部类(推荐)
public class Singleton {
//1.将构造方法私有化
private Singleton(){}
//写一个静态内部类,该类中有一个私有的静态实例
private static class SingletonHolder{
//静态初始化器,由JVM保证线程安全
private static Singleton ourInstance = new Singleton();
}
//留给外界初始化的方法,返回该对象
public static Singleton getInstance(){
return SingletonHolder.ourInstance;
}
}
1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2) 静态内部类在该类需要实例化,调用getInstance 方法时,才会装载SingletonInstance类,从而完成Singleton的实例化。
3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4) 优点:这种方法基于在懒汉模式中提出的,JVM在类的初始化阶段给出的线程安全性保证,将singleton 的实例化操作放置到一个静态内部类中,在第一次调用getInstance() 方法时,JVM才会去加载InstanceHolder 类,同时初始化singleton 实例,因此,即使我们不采取任何同步策略,getInstance() 方法也是线程安全的。
5) 结论:线程安全,效率高,推荐使用。
枚举(推荐)
这种方式是Effective Java 作者Josh Bloch 提倡的方式,他说最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。
//使用枚举实现单例, 推荐
public enum Singleton {
INSTANCE;
public void sayOK() {
System.out.println("ok~");
}
}