和往常一样,我们首先推荐整理一个长期且非常实用的面试题库:
正文如下,如果觉得有用请点赞关注~~
单例模式是最常用的设计模式之一,熟悉设计模式的朋友对单例模式不会陌生。 一般介绍单例模式的书籍都会提到饿汉和懒汉两种实现方式。 但是除了这两种方式,本文还会介绍其他几种实现单例的方式,一起来看看吧。
介绍
单例模式是一种常用的软件设计模式,它的定义是单例对象的类只能允许一个实例存在。
在很多情况下,整个系统只需要有一个全局对象,这有助于我们协调整个系统的行为。 例如,在服务器程序中,服务器的配置信息保存在一个文件中,这些配置数据统一由一个单例对象读取,然后服务进程中的其他对象通过这个单例对象获取配置信息。 这种方法简化了复杂环境中的配置管理。
基本实现思路
单例模式要求类有一个返回对象的引用(总是相同的)和一个获取实例的方法(必须是静态方法,通常使用名称getInstance)。
单例的实现主要通过以下两步:
防范措施
在多线程应用程序中必须谨慎使用单例模式。 如果两个线程在唯一实例还没有创建的时候同时调用create方法,它们就不能同时检测到唯一实例的存在,从而各自同时创建一个实例,这样两个构造实例,从而违反了实例模式中的唯一实例的原则。
这个问题的解决方案是为指示该类是否已被实例化的变量提供一个互斥体(尽管这样效率较低)。
单例模式的八种写法 1.饿了么中国式(静态常量)【可用】
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
优点:这种写法比较简单,就是类加载的时候实例化就完成了。 避免了线程同步问题。
缺点:在加载类的时候就完成了实例化,没有达到Lazy Loading的效果。 如果这个实例从头到尾都没有被使用过,就会造成内存的浪费。
2.饿了么中国风(静态代码块)【可用】
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种方法其实和上面的方法类似,只是将类实例化的过程放在了一个静态代码块中,当类加载时,会执行静态代码块中的代码来初始化类的实例。 优缺点同上。
3.惰性风格(线程不安全)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种写法有Lazy Loading的效果,但是只能在单线程下使用。 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还没来得及执行,另一个线程也通过了判断语句,此时会产生多个实例。 因此,这种方法不能用于多线程环境。
4.惰性风格(线程安全、同步方式)【不推荐】
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
解决上面第三种实现方式的线程不安全问题,做线程同步就可以了,所以线程同步是在getInstance()方法上进行的。
缺点:效率太低。 当每个线程想要获取类的实例时3D场景,必须在执行getInstance()方法时进行同步。 其实这个方法只执行了一次实例化代码2d素材,后面如果想获取这个类的实例,直接return即可。 该方法同步效率太低,需要改进。
5.惰性风格(线程安全,同步代码块)[不可用]
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
由于第四种实现方式的同步效率太低,因此放弃同步方式,改为同步生成实例化代码块。 但是这种同步并没有起到线程同步的作用。 与第三种实现方式中遇到的情况一致,如果一个线程进入了if(singleton == null)判断语句块,以后还没来得及执行unity单例模式,另一个线程也通过了这个判断语句,那么就会产生多个实例。
6.仔细检查[推荐]
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Double-Check 的概念对于多线程开发者来说并不陌生。 如代码所示,我们进行了两次if(singleton == null)检查,这样可以保证线程安全。
这样实例化代码只需要执行一次,后面再次访问时判断if(singleton == null),直接返回实例化对象。
优点:线程安全; 延迟加载; 高效率。
7.静态内部类【推荐】
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
这种方式和饿了么中文方式采用的机制类似,但又有所不同。 两者都使用了类加载的机制来保证初始化实例时只有一个线程。 不同的是饿汉方法只要加载Singleton类就实例化,没有Lazy-Loading的影响,而静态内部类方法不会在Singleton类加载时立即实例化,而是在加载Singleton类时实例化需要调用getInstance方法时,会加载SingletonInstance类,完成Singleton的实例化。
类的静态属性只有在类第一次加载时才会被初始化,所以在这里unity单例模式,JVM帮我们保证了线程的安全。 当类初始化时,其他线程无法进入。
优点:避免线程不安全,懒加载,效率高。
8.枚举【推荐】
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
单例模式是借助JDK1.5中新增的枚举实现的。 不仅可以避免多线程同步问题,还可以防止反序列化重新创建新对象。 可能是因为枚举是在JDK1.5中加入的,所以在实际的项目开发中,很少有人这么写。
优点:该类的对象在系统内存中只有一个,节省了系统资源。 对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。
缺点:当你要实例化一个单例类时,一定要记得使用相应的获取对象的方法,而不是使用new,这可能会给其他开发者带来麻烦,尤其是在看不到源码的情况下。
应用