单例模式 - SingleTon
【介绍】单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。这种不能自由构造对象的情况,就是单例模式的使用场景。
【定义】确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
【使用场景】确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,比如访问IO、数据库等资源。
【关键点】
①构造函数不对外开放,一般为Private;
②通过一个静态方法或者枚举返回单例类对象;
③确保单例类的对象有且只有一个,尤其在多线程环境下;
④确保单例类对象在反序列化时不会重新构建对象。
通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。
单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保线程安全(在多线程环境下构造单例类的对象也是有且只有一个)。
【实现方式】
A. 饿汉式
// 单例模式 - 饿汉式
public class SingleTon {
// 在声明静态对象时就对其初始化
private static final SingleTon instance = new SingleTon();
// 构造函数私有
private SingleTon(){
}
// 公有的对外静态方法,对外暴露获取单例对象的接口,等用户调用该方法时直接返回已经实例化的单例对象
public static SingleTon getInstance(){
return instance;
}
}
public class Test {
public static void main(String[] args){
// SingleTon对象只能通过对外暴露获取单例对象的公共方法“getInstance()”获取到
SingleTon singleTon = SingleTon.getInstance();
}
}
B. 懒汉式
// 单例模式 - 懒汉式
public class SingleTon{
// 声明一个静态对象先不实例化,等用户第一次调用公共方法“getInstance()”时再进行实例化
private static SingleTon instance;
// 构造函数私有
private SingleTon(){}
// 公有的对外静态方法,对外暴露获取单例对象的接口,等用户第一次调用该方法时再进行实例化
// 【同步方法】可在多线程情况下保证单例对象的唯一性。
public static synchronized SingleTon getInstance(){
if (instance == null){
instance = new SingleTon();
}
return instance;
}
}
小结:
懒汉优点:单例只有在使用时才会被初始化,在一定程度上节约了资源;
懒汉缺点:第一次加载时需要进行初始化,反应较慢;
最大问题:每次调用“getInstance()”方法时都进行同步,造成不必要的同步开销。
因此这种模式一般不建议使用。
C. Double Check Lock (DCL)
// 单例模式 - Double Check Lock (DCL)
public class SingleTon{
// 声明一个null静态对象先不实例化,等用户第一次调用公共方法“getInstance()”时再进行实例化
private static SingleTon sInstance = null;
// 构造函数私有
private SingleTon(){}
// 公有的对外静态方法,对外暴露获取单例对象的接口,等用户第一次调用该方法时再进行实例化
// 两层判空是应为涉及到java编译器编译时的问题。
public static SingleTon getInstance(){
// 第一层判空是为了避免不必要的同步
if (mInstance == null){
synchronized (SingleTon.class){
// 第二层判空是为了在null的情况下创建实例
if (mInstance == null){
sInstance = new SingleTon();
}
}
}
return sInstance;
}
public void doSomething(){
System.out.println("do something..");
}
}
小结:
优化:优化了懒汉方式下过多的同步开销;
优点:资源利用率高,第一次执行“getInstance()”方法时单例对象才会实例化,又能保证线程安全,且不会产生不必要的同步锁;
缺点:第一次加载时反应稍微慢,偶尔会失败,在高并发环境下也有小概率的缺陷。
DCL方式是使用最多的单例实现方式,除非你的并发场景比较复杂或者低于JDK 6版本。
D. 静态内部类
// 单例模式 - 静态内部类
public class SingleTon{
// 构造函数私有
private SingleTon(){}
// 公有的对外静态方法,对外暴露获取单例对象的接口
// 【优化】第一次加载“SingleTon类”时不会初始化instance对象,只有等用户第一次调用该方法时才会导致虚拟机加载“内部类SingleTonHolder”导致instance被初始化
public static SingleTon getInstance(){
return SingleTonHolder.instance;
}
// 静态内部类 - 加载该类才会导致instance被初始化。①保证线程安全;②保证单例对象唯一;③延迟单例的实例化
private static class SingleTonHolder{
private static final SingleTon instance = new SingleTon();
}
}
小结:
优化:优化了DCL方式在某些情况下失效(双重检查锁定失效)的问题;
优点:①保证线程安全;②保证单例对象唯一;③延迟单例的实例化;
因此,该方式是值得推荐的单例模式实现方式。
E. 枚举
// 单例模式 - 枚举
public enum SingleTon{
// 枚举在java中不仅可以拥有字段
INSTANCE;
// 枚举在java中还可以拥有自己的方法
public void doSomething(){
System.out.println("do something..");
}
}
小结:
优化:优化了上述几种单例实现方式在反序列化的情况下会重新创建对象;
优点:①写法超级简单;②线程安全;③任何情况下它都是一个单例;