一、什么是单例设计模式
在写单例设计模式前,首先需要了解什么是单例设计模式:单例模式,指的是一个类只有一个实例,并且有一个全局可以访问的接口。他有以
下几个优点:
(1)可以节省内存和计算,很多时候我们其实只需要一个实例就够了,多个实例会造成浪费,比如对于一些初始化比较耗时的类,就只用生成
一个实例保存在内存中供大家一起实用,没有必要多次生成新的实例。
(2)单例设计模式可以保证全局计算结果的正确,比如一个全局计数器用于统计人数,如果有多个计数器反而会造成混乱。
(3)方便管理,对于很多工具类,我们生成一个实例,然后通过一个统一的入口来获取这个单例,太多实例反而会眼花缭乱。
二、单例设计模式适用场景
- 无状态的工具类,如日志工具类,我们不需要他来帮我们存储,只需要他来记录日志信息,这时候只需要一个实例就可以了。
- 全局信息类,如环境变量类,比如统计一个网站的浏览量,可以让这个类变成一个单例,在需要计数的时候拿出来用就可以了。
三、常见的单例模式写法
1. 饿汉式
public class SingletonEH {
private static SingletonEH singletonEH = new SingletonEH();
private SingletonEH(){};
public static SingletonEH getInstance(){
return singletonEH;
}
}
特点:在类装载的时候完成实例化,避免了线程同步问题
缺点:如果从始至终没有使用到该实例,会造成内存浪费
2. 懒汉式
线程不安全的写法
public class SingletonLH {
private static SingletonLH singletonLH;
private SingletonLH(){}
public static SingletonLH getInstance(){
if (singletonLH == null){
singletonLH = new SingletonLH();
}
return singletonLH;
}
}
特点:起到了懒加载的作用,在调用getInstance()
方法后才去实例化对象。
缺点:只能在单线程下使用。多线程情况下,如果一个线程进入了if (singletonLH == null)
判断,还没有实例化的时候,另一个线程也进入了判断语句,这时候就会多次创建实例。
线程安全的写法
public class SingletonLH {
private static SingletonLH singletonLH;
private SingletonLH(){}
public static synchronized SingletonLH getInstance(){
if (singletonLH == null){
singletonLH = new SingletonLH();
}
return singletonLH;
}
}
缺点:效率太低,为了让懒汉式线程安全且效率高,提出了双重检查式
3. 双重检查式
public class SingletonLH {
private static volatile SingletonLH singletonLH;
private SingletonLH(){}
public static SingletonLH getInstance(){
if (singletonLH == null){
synchronized (SingletonLH.class){
if (singletonLH == null){
singletonLH = new SingletonLH();
}
}
}
return singletonLH;
}
}
特点:线程安全,效率高
缺点:无法防止被反序列化生成多个实例
两个判断如果去掉第一个,会导致所有线程串行执行,效率低下。如果去掉第二个,可能会出现多个线程同时进入判断,从而产生多个实例。并没有保证线程安全,因此两个判断缺一不可。
需要注意的地方是volatile
来修饰了变量。它能有效防止重排序的发生,保证持有同一个对象锁的两个同步块只能串行执行,这样可以避免可能存在的一种情况:线程一先指向单例对象的内存地址(此时对象已经不是null了),但是还未初始化,这时候线程二进来了,且通过了是否为null的判断,将还未初始化的对象返回使用。这种情况会报错
4. 静态内部类式
public class SingletonStatic {
private SingletonStatic(){}
private static class SingletonStaticInstance{
private static final SingletonStatic singletonStatic = new SingletonStatic();
}
public static SingletonStatic getInstance(){
return SingletonStaticInstance.singletonStatic;
}
}
特点:跟饿汉式类似,采用类装载的时候实例化对象,区别在于饿汉式是在类加载的时候实例化,静态内部类式是在调用了getInstance()
方法的时候才会去完成对该实例的实例化,和双重检查式一样,线程安全且效率高
缺点:无法防止被反序列化生成多个实例
5. 枚举式(推荐)
public enum SingletonEnum {
INSTANCE;
public void whateverMethod(){
}
}
优点:简洁,线程安全,能防止序列化和反射创建多个实例从而破坏单例。