网站域名怎么买,wordpress localhost,建电子商务网站,自己做的网站怎么链接火车头采集设计模式的分类
我们都知道有 23 种设计模式#xff0c;这 23 种设计模式可分为如下三类#xff1a;
创建型模式#xff08;5 种#xff09;#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式#xff08;7 种#xff09;#xff1…设计模式的分类
我们都知道有 23 种设计模式这 23 种设计模式可分为如下三类
创建型模式5 种单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式7 种适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型模式11 种策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 设计模式系列文章传送门
设计模式的 7 大原则
什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式在程序中多次使用同一个对象时为了防止频繁地创建对象使得 JVM 内存飙升单例模式可以让程序仅在内存中创建一个对象让所有需要调用的地方都共享这一单例对象这种模式涉及到一个单一的类该类负责创建自己的对象同时确保只有单个对象被创建这个类提供了一种访问其唯一的对象的方式可以直接访问不需要实例化该类的对象。
单例模式的分类
单例模式有两种类型如下
饿汉式类加载时候就会创建该类的单例对象。懒汉式类加载时候并不会创建单例对象只有首次调用该对象时才会创建。
静态变量实现单例模式
静态变量实现单例模式是一种饿汉式的实现代码如下:
public class StaticSingleton {//私有构造方法private StaticSingleton() {}//创建静态单例对象是一个成员变量private static final StaticSingleton STATIC_SINGLETON new StaticSingleton();//对外提供静态方法获取静态单例对象public static StaticSingleton getInstance() {return STATIC_SINGLETON;}}静态变量单例模式的关键是类的构造方法私有同时创建一个静态私有的实例对象通过静态方法来让调用者来获取该单例对象同时该类加载的时候就会创建该单例对象。
静态代码块实现单例模式
静态代码块实现单例模式和静态变量实现单例模式基本相同也是一种饿汉式的实现代码如下:
public class StaticBlockSingleton {//私有构造方法private StaticBlockSingleton() {}//创建静态单例对象是一个成员变量private static final StaticBlockSingleton STATIC_SINGLETON;//静态代码块创建实例对象static {STATIC_SINGLETON new StaticBlockSingleton();}//对外提供静态方法获取静态单例对象public static StaticBlockSingleton getInstance() {return STATIC_SINGLETON;}}懒汉单例模式–线程不安全
线程不安全的懒汉单例模式实现代码如下
public class NotSafeSingleton {//私有构造方法private NotSafeSingleton() {}//定义一个静态私有的成员变量private static NotSafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式public static NotSafeSingleton getInstance() {if (staticSingleton null) {staticSingleton new NotSafeSingleton();}return staticSingleton;}}为什么说上面这种写法不安全呢
乍一看确实没有什么问题我们提供了一个静态方法供调用者获取单例对象在静态方法中我们先进行了对象为 null 判断似乎没有什么问题在没有并发的情况下确实没有问题但是如果有并发呢两个调用者线程同时进入了 staticSingleton null 判断此时谁也没有执行对象的创建因此都可以继续往下执行 new NotSafeSingleton() 这句代码至此这种单例模式为什么不是线程安全的已经很明了了我们在项目开发中也要考虑这种并发的场景。
懒汉单例模式–线程安全
上面分享了线程不安全的懒汉模式实现现在我们分享一下线程安全的懒汉模式这里使用 synchronized 来保证线程安全代码如下
public class SafeSingleton {//私有构造方法private SafeSingleton() {}//定义一个静态私有的成员变量private static SafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static synchronized SafeSingleton getInstance() {if (staticSingleton null) {staticSingleton new SafeSingleton();}return staticSingleton;}}使用了 synchronized 之后就不会出现两个线程同时执行 staticSingleton null 的判断了因此也就没有了线程安全问题。
懒汉单例模式–双重检查Double Check
上面分享了使用 synchronized 来实现线程安全的懒汉单例模式现在我们分享一种更优雅的线程安全的懒汉模式代码如下
public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton null) {//加锁synchronized (DoubleCheckSingleton.class){//第二次为空判断if(staticSingleton null){staticSingleton new DoubleCheckSingleton();}}}return staticSingleton;}}对比上面直接在方法上使用了 synchronized这里调整了 synchronized 的位置让锁的范围变的更小同时也提升了效率因此懒汉单例模式真正发生线程不安全的地方就是创建对象的那行代码而创建的操作只需要进行一次就可以了单量的请求都不会走到创建对象的这行代码因此我们没必要把 synchronized 加在方法上因此就有了双重检查的懒汉单例模式为什么需要双重检查这种双重检查懒汉单例模式真的就是完美的吗
为什么需要双重检查
假设有线程 1 和现成 2 都来调用 getInstance 方法获取单例对象进入第一个 staticSingleton null 的判断时候线程 1 和线程 2 都满足条件都可以继续往下执行。接着线程 1 获取到锁再次进行 staticSingleton null 的判断此时 staticSingleton 任然为 null线程 1 就会开始创建对象线程 2 等待获取锁。线程 1 创建完对象后释放锁线程 2 获取到锁后来执行 staticSingleton null 判断因为线程 1 已经创建了对象staticSingleton 不会 null线程 2 就不会再次创建对象了。
至此我们就知道双重检查的必要性了。
双重检查懒汉单例模式真的就是完美的吗
双重检查的饿汉单例模式看起来是很完美实际上至少在 99.99% 的场景也不会出问题但是它并不 100 完美我们来回忆一下对象创建的过程如下
在 JVM 堆上开辟地址空间。把开辟的地址空间赋值给 Java 虚拟机栈上的变量到这里对象以及不为 null 了因此完成了对象实例化。初始化对象完成属性赋值。
总结来说就是对象先实例化后初始化对象实例化之后因为在 JVM 堆上开辟了内存空间因此就不会 null 了。
双重检查的饿汉单例模式的问题就出现在对象的实例化和初始化上本质上对象的实例化和初始化没有什么问题但是 JVM 又有一个指令重排的操作具体来说就是对象分配了地址空间之后初始化之前把对象的地址赋值给了静态成员变量 staticSingleton而恰巧此时其他线程调用了 getInstance 方法执行第一个 staticSingleton null 判断时候就发现 staticSingleton 不为 null就直接返回 staticSingleton 给调用者使用结果可想而知会发生空指针异常那如何来解决这个问题呢
volatile 关键字
一个变量如果被 volatile 关键字修饰后就会具备一下两项能力
保证线程间的可见性当一个线程对共享变量进行了修改其他线程可以立即看到修改后的最新值volatile 能让行缓存无效因此能读到内存中最新的值。禁止进行指令重排序用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障阻止其他读写操作越过屏障从而达到阻止重排序的效果。
上面我们谈到因为指令重排导致了 staticSingleton 对象已经不会 null 了从而导致了调用者最终的空指针异常我们了解了 volatile 关键字的作用后对双重检查饿汉单例模式进行升级代码如下
public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static volatile DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton null) {//加锁synchronized (DoubleCheckSingleton.class) {//第二次为空判断if (staticSingleton null) {staticSingleton new DoubleCheckSingleton();}}}return staticSingleton;}}饿汉单例模式–静态内部类
前面我们分享的静态相关的单例模式都是懒汉模式而静态内部类的单例模式是饿汉的代码如下
public class StaticInnerClassSingleton {//私有构造方法private StaticInnerClassSingleton() {}//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 静态方法中调用静态内部内的静成员变量public static StaticInnerClassSingleton getInstance() {return SingletonHolder.STATIC_SINGLETON;}//静态内部类private static class SingletonHolder {//在内部类中创建一个 单例对象private static StaticInnerClassSingleton STATIC_SINGLETON new StaticInnerClassSingleton();}}可以看到静态内部类方式实现单例模式最终对象的创建是由静态内部类创建的因为 JVM 在加载外部类的过程中并不会去加载静态内部类只有当静态内部类被的属性、方法被调用时候才会进行初始化可以看到我们在使用静态内部类实现恶汉单例模式并没有加锁而且也是线程安全的这表名静态内部类实现的单例模式是一种不错的选择。
饿汉单例模式–枚举
使用枚举类实现单例模式其实是最单例实现模式实现因为枚举类型是线程安全的并且只会装载一次使用枚举实现单例模式充分的利用了枚举的特性枚举的写法非常简单代码如下
public enum EumSingleton {INSTANCE;
}
枚举实现单量模式不仅简单而且使用枚举实现单例模式是所用单例模式实现中唯一不会被破坏的单例实现模式反射可以破坏单例模式。
总结本篇简单分享了单例模式的几种实现方式并对各种单例模式的实现方式进行了剖析希望可以帮助到有需要的朋友。
如有不正确的地方欢迎各位指出纠正。