1.什么是单例
单例或称单例规划形式(Singleton Design Pattern),23种规划形式之一创立型形式的一种,运用频率极高,是必须了解的规划形式之一;单例的责任归纳起来很简单那就是确保某一个类只能有一个实例以供大局运用(Ensure a class only has one instance, and provide a global point of access to it)
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) {
//第2次判空
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
在该方法下,咱们运用volatile
关键字确保了单例目标的同享可见,其中两次的判空起到的效果也不同,第一次判空是判断是已经否存在单例目标,第2次判空合作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
能够快速创立单例类 - 单例是能够被损坏的(利用反射等其他办法)
6.参考
java-design-patterns
《深化规划形式》