做铝板的网站,数字营销专业,wordpress 腾讯地图插件,黄冈网站推广都有哪些渠道单例模式是应用最广的设计模式之一#xff0c;也是程序员最熟悉的一个设计模式#xff0c;使用单例模式的类必须保证只能有创建一个对象。
今天主要是回顾一下单例模式#xff0c;主要是想搞懂以下几个问题
为什么要使用单例#xff1f; 如何实现一个单例#xff1f; 单…单例模式是应用最广的设计模式之一也是程序员最熟悉的一个设计模式使用单例模式的类必须保证只能有创建一个对象。
今天主要是回顾一下单例模式主要是想搞懂以下几个问题
为什么要使用单例 如何实现一个单例 单例存在哪些问题 单例对象的作用域的范围 单例模式是如何保证唯一性的
一、为什么要使用单例
在开发过程中很多时候一个类我们希望它只创建一个对象比如线程池、缓存、网络请求等。当这类对象有多个实例时程序就可能会出现异常比如程序出现异常行为、得到的结果不一致等。
这时候就应该使用单例模式。
单例主要有这两个优点
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象因此可以节约系统资源对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
二、实现单例的 5 种方式
实现单例模式主要有以下几个关键点
构造函数设置为 private 这避免外部通过 new 创建实例 通过一个静态方法或者枚举返回单例类对象 考虑对象创建时的线程安全问题确保单例类的对象有且仅有一个尤其是在多线程环境下 确保单例类对象在反序列化时不会重新构建对象。 考虑是否支持延迟加载 下面是常见的集中单例模式的实现方式
1、饿汉式 在类加载的期间就已经将 instance 静态实例初始化好了所以instance 实例的创建是线程安全的。不过这样的实现方式不支持延迟加载实例。
public class Singleton {private Singleton(){}private static final Singleton instance new Singleton();public static Singleton getInstance(){return instance;}
}2、懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
public class Singleton {private Singleton(){}private static Singleton instance;public static synchronized Singleton getInstance(){if (instance null) {instance new Singleton();}return instance;}
}但它的缺点也很明显getInstance 使用了 synchronize 实现线程同步导致这个方法的并发很低每次调用都会频繁的枷锁、释放锁会导致性能瓶颈。
3、双重检测 饿汉式不能延时加载懒汉式有性能问题而双重检测方式既支持延迟加载、又支持高并发的单例实现方式。
当 instance 对象被创建后再次调用 getInstance 方法不再会进入 synchronize 加锁的代码之中。
它的优点是资源利用率高第一次执行 getInstance 时才会被实例化效率高。缺点是第一次加载反应稍慢。
public class Singleton {private Singleton(){}private static Singleton instance;public static Singleton getInstance(){if (instance null) {synchronized (Singleton.class) {if (instance null) {instance new Singleton();}}}return instance;}
}有时候面试官会问这种实现方式有什么问题。他们指的就是指令重排序。
instance new Singleton(); 并不是一个原子操作 这句代码实际执行了三件事。
1、 给 Singleton 的实例分配内存
2、调用 Singleton 的构造函数初始化成员变量
3、将 instance 的对象指向分配的内存空间。
因为 Java 编译器允许处理器乱序执行2、3的顺序是无法保证的。如果是 1-3-2 执行的顺序当执行完 3 、2未执行之前被切换到 B 线程此时 instance 已经非空B 会直接取走 instance在使用时就会出错。
这就是指令重排。
解决办法也很简单只需要给 instance 成员变量加上 volatile 关键字就可以禁止指令重排序。
其实这个问题在高版本的 java 中已经被解决了解决方式也很简单就是把对象 new 操作和初始化操作设计为原子操作就自然能禁止重排序。
4、静态内部类 除了以上方法外使用 Java 的静态内部类也能够实现。
public class Singleton {private Singleton(){}private static class Instance {private static final Singleton instance new Singleton();} public static Singleton getInstance(){return Instance.instance;}
}当第一次加载 Singleton 类时并不会初始化 instance只有在第一次调用 Singleton 的 getInstance 方法时才会导致 instance 被初始化。
第一次调用 getInstance 方法时会导致虚拟机加载 Instance 类这种方式不仅能保证线程安全也能够保证单例对象唯一同时也延迟了单例的实例化。
5、枚举 枚举是单例最简单的实现方式这种实现方式通过 Java 枚举类型本身的特性保证了实例创建的线程安全性和实例的唯一性。
public enum Singleton {INSTANCE;
}三、单例存在哪些问题
1、对 OOP 特性的支持不友好 面向对象的四大特征是封装、继承、多态。单例对继承、多态特性的支持不友好。
虽然从理论上来讲单例类也可以被继承、也可以实现多态但实现起来会非常奇怪。所以一旦将某个类设计成到单例类也就意味着放弃了继承和多态这两个面向对象的特性也就相当于损失了可以应对未来需求变化的扩展性。
2、单例对代码的扩展性不友好 我们知道单例类只能有一个对象实例。但如果未来改需求了需要创建两个或多个实例就需要对代码有比较大的改动。
3、单例不支持有参数的构造函数 单例不支持有参数的构造函数如果想要传递参数只能在 getInstance 方法中添加参数或者定义方法传递参数。
4、针对这些问题有何替代的解决方案 为了保证全局唯一除了使用单例还可以用静态方法来实现。不过静态方法这种实现思路并不能解决之前提到的问题。
实际上它比单例更加不灵活比如它无法支持延迟加载。
目前并没有什么很好的方式来解决。
四、单例对象的作用域的范围
单例模式的类只能创建一个对象这个对象的作用域是整个 APP 的生命周期也就是进程中唯一。
当我们打开 APP 后系统会开启一个进程并分配给 APP接着进程会一条一条地执行 APP 文件中包含的代码比如 当读到 User user new User(); 这条语句的时候它就在自己的地址空间中创建一个 user 临时变量和一个 User 对象。
进程之间是不共享地址空间的如果我们的 APP 开启多个进程那么每个进程都会分配新的地址空间单例模式就会失效。。
单例类中对象的唯一性的作用范围是进程内的在进程间是不唯一的。
五、单例模式是如何保证唯一性的
这里就需要了解JVM 的类加载机制。
虚拟机的类加载是采用双亲委派模型。
它的工作过程是如果一个类加载器收到了类加载的请求它首先不会去加载这个类而是把这个请求委派给父类加载器去完成每一个层次的类加载器都是如此因此所有的加载请求最终都应该传送到顶层的启动类加载器中只有当父加载器反馈自己无法完成这个加载请求他的搜索范围中没有找到这个类子加载器才会去尝试加载。 所以当单例模式的类被实例化后因为这个类已经加载过了再次请求加载时就不会创建新的而是会找到已经存在的这个类。
本文就到这里单例模式虽然比较常用但是它的知识点还是挺多的。