1.什么是单例
单例或称单例设计模式(Singleton Design Pattern),23种设计模式之一创建型模式的一种,使用频率极高,是必须了解的设计模式之一;单例的职责概括起来很简单那就是**保证某一个类只能有一个实例以供全局使用
2.为什么需要单例
单例类的特殊性使得我们可以在一定的程度上避免了频繁创建销毁对象时所带来的开销、全局访问到的对象都是同一个(如实现网络请求、数据库操作等)这也就代表着可以实现资源的共享、避免多个实例竞争使用同一资源等等。
3.如何实现单例
实现单例大致可以分为以下这几种:
- 饿汉模式
- 懒汉模式
- 静态内部类创建
- Kotlin实现
- 枚举类 下面,我将一一举例如何实现一个单例类(为了方便类名统一称为
Singleton
)
饿汉模式
public class Singleton {
//饿汉模式
private static Singleton instance = new Singleton();
//不暴露无参构造
private Singleton() {
}
//获取实例
public static Singleton getInstance() {
return instance;
}
}
饿汉模式顾名思义嘛,一上来就直接将实例创建完成,利用JVM特性使得该过程是线程安全的,但是缺点还是显而易见的,静态变量不能实现懒加载,造成了资源上的浪费。
懒汉模式
懒汉模式相比于饿汉模式就突出在懒上即是延迟初始化,但是实现方式的不同决定了懒汉模式是否是线程安全的。
平平无奇懒汉
public class Singleton {
//懒汉模式
private static Singleton instance;
//不暴露无参构造
private Singleton() {
}
//获取实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
看的出来,懒汉模式下的单例可以在我们需要使用的时候才初始化单例对象,但是上述方法中存在一个问题,那就是在多线程并发的情况下不能保证创建单例对象的唯一性(线程不安全)。
线程安全懒汉
public class Singleton {
//懒汉模式(线程安全)
private volatile static Singleton instance; //volatile修饰 保证修改的值会立即被更新到主存
private Singleton() {}
//双重检查
public static Singleton getInstance() {
//第一次判空
if (instance == null) {
synchronized (Singleton.class) {
//第二次判空
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
在该方式下,我们使用volatile
关键字确保了单例对象的共享可见,其中两次的判空起到的作用也不同,第一次判空是判断是已经否存在单例对象,第二次判空配合synchronized
确保单例对象的一致性,保证了线程安全。
静态内部类
public class Singleton {
private Singleton() {}
//向外暴露获取实例的方法
public static Singleton getInstance() {
return InnerSingleton.instance;
}
//内部类
private static class InnerSingleton {
private static final Singleton instance = new Singleton();
}
}
静态内部类可以说是结合了饿汉模式和懒汉模式的优点,利用了类初始化的时候,不会去初始化静态内部类,而是只有在你去调用了 getInstance()
方法的时候,才选择初始化,同时利用JVM特性也保证了单例对象创建的线程安全。
Kotlin中的单例
object SingletonKt {
//.....
}
你没看错,Kotlin实现单例仅仅只需要将class替换为object
就可以了,为什么能这么轻松的实现呢?我们反编译一下就能看出其实也是使用了静态内部类的方式实现的单例。
public final class SingletonKt {
@NotNull
public static final SingletonKt INSTANCE;
private SingletonKt() {
}
static {
SingletonKt var0 = new SingletonKt();
INSTANCE = var0;
}
}
枚举
最后一个就是我们的枚举类,枚举是一种特使的单例,它可以保证单例无法被反射破坏。
public enum Singleton {
INSTANCE
}
4.如何破坏单例
反射
利用反射可以破坏单例:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
//获取私有无餐构造对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建singleton对象
Singleton instance1 = (Singleton) constructor.newInstance();
Singleton instance2 = (Singleton) constructor.newInstance();
System.out.println(instance2);
System.out.println(instance1);
}
结果:
singleton.Singleton@723279cf
singleton.Singleton@10f87f48
可以看到利用反射成功破坏了单例。
5.总结
- 使用单例的目的是为了保证其对象的全局唯一性
- 设计单例的时候需要考虑到懒加载以及线程安全问题
- 使用Kotlin中
object
可以快速创建单例类 - 单例是可以被破坏的(利用反射等其他方法)