盐城网站建设推广,收录优美的图片,wordpress reset,又一地发现2例阳性当谈及单例模式时#xff0c;我们指的是一种设计模式#xff0c;它确保某个类只有一个实例#xff0c;并提供一个全局访问点。在Java中#xff0c;单例模式是最常用的设计模式之一#xff0c;它可以确保一个类在应用程序的生命周期内只有一个实例#xff0c;并提供全局访…当谈及单例模式时我们指的是一种设计模式它确保某个类只有一个实例并提供一个全局访问点。在Java中单例模式是最常用的设计模式之一它可以确保一个类在应用程序的生命周期内只有一个实例并提供全局访问点以便访问该实例。
1. 为什么要使用单例模式
单例模式主要用于以下几种情况
当一个类只能有一个实例且客户端需要访问这个实例时可以使用单例模式。当希望避免由于创建太多对象导致的性能问题时可以使用单例模式。当需要控制资源的访问权限时可以使用单例模式。
2. 实现单例模式的方式
在Java中有多种方式可以实现单例模式下面我们将介绍其中比较常见的三种实现方式懒汉式、饿汉式和双重检查锁定。
2.1 饿汉模式
饿汉模式是指在类加载时就创建实例由于这个实例创建的非常早所以使用饿汉描述形容非常迫切。这种方式在多线程环境下也能保证单例的唯一性。 示例代码
class Singleton1 {private static Singleton1 instance new Singleton1();public static Singleton1 getInstance() {return instance;}private Singleton1() {};
}
在上述代码中我们可以看到我们在类属性中实例化了一个该类的对象然后给出了一个获取这个对象的方法然后我们把默认的无参构造方法实现为 “私有的”让外界无法直接实例化新的对象。
2.2懒汉模式
懒汉式单例是指在需要时才创建实例。当第一次调用获取实例的方法时才会创建实例对象。
示例代码
class SingletonLazy {private static SingletonLazy instance null;private SingletonLazy() {}public static SingletonLazy getInstance() {if(instance null) {instance new SingletonLazy();}return instance;}
}
如果首次调用getInstance() 此时instance为null就会进入if条件从而创建实例等再次调用getInstance则不会再创建实例。
但是上如代码如果在多线程中调用是有可能创建多个实例的
假设有两个线程 t1, 和 t2 都在调用 getInstance 当 t1 刚好执行到进入 if 内部 还没有 把创建好的实例赋值给 instance 的时候被调度除了cpu此时instance 仍然等于 null, t2线程就有可能进入 if 内从而再次创建一个实例。
如何改进让上述代码变为线程安全的代码
上诉代码线程不安全的原因是if 和 new 操作之间可能再次运行了其他线程的 if 和 new 操作于是我们可以通过加锁把这两个操作打包在一起
class SingletonLazy {private static SingletonLazy instance null;private SingletonLazy() {}private static Object locker new Object();public static SingletonLazy getInstance() {synchronized (locker) {if (instance null) {instance new SingletonLazy();}}return instance;}
}
上述代码就保证了 if 和 new 操作之间 不会执行其他线程的 if 和 new 操作。但是我们发现上述代码其实只有在第一次调用的时候才需要加锁后续都直接返回 instance 即可 但是每次仍然都是先加锁再解锁这样会导致该代码的效率会变低于是我们可以在该方法的上面再加一个 if 判断
class SingletonLazy {private static SingletonLazy instance null;private SingletonLazy() {}private static Object locker new Object();public static SingletonLazy getInstance() {if(instance null) {//如果 instance 为 null 说明是首次调用需要加锁synchronized (locker) {if (instance null) {instance new SingletonLazy();}}}return instance;}
}
注意上述代码中两个 if 的结果是可能不同的。第一个 if 和 第二个 if 之间可能 会执行其他 线程调用 的 getInstance 导致 instance 不为 null。
上述代码 虽然保证了 if 和 new 操作之间 不会执行其他线程的 if 和 new 操作但仍可能存在线程安全问题。
指令重排序引起的线程安全问题
指令重排序是指处理器在执行指令时可能会改变指令的顺序以提高程序的运行效率。
我们来看这行代码
instance new SingletonLazy();
这行代码可以拆分为三个步骤不是三个cpu指令
申请一段内存空间在这个内存上创建出实例把这段空间的内存地址赋值给 instance
正常情况下上述步骤是按 1 2 3 的顺序来执行的但是编译器也可能优化成 1 3 2来执行 现在假设有 t1 , t2 两个线程 如果此时t1 正在以 1 3 2的顺序在执行这行代码t1在刚执行了 1 和 3 之后 就被调度出了cpu 此时instance 的值已经不为空但是指向的内存中却还没有创建 实例此时 t2 线程又刚好开始执行 getInstance 于是t2 线程就得到了一块没有被初始化的内存空间此时如果t2 再尝试使用这块内存空间中的内容就会引发错误 此时的instance是一个“全0”的值。
所以我们 应该给 instance 加上 volatile 关键字 来禁止指令重排序
class SingletonLazy {private volatile static SingletonLazy instance null;private SingletonLazy() {}private static Object locker new Object();public static SingletonLazy getInstance() {if(instance null) {//如果 instance 为 null 说明是首次调用需要加锁synchronized (locker) {if (instance null) {instance new SingletonLazy();}}}return instance;}
} 现在上述代码就是一个线程安全的代码了
2.3 懒汉模式和饿汉模式的优缺点
懒汉式单例模式 优点
延迟加载只有在首次调用getInstance()方法时才会创建实例可以节约资源。线程安全双重检查锁定通过使用synchronized关键字和双重检查锁定机制可以保证多线程环境下的线程安全性。
缺点
可能存在线程安全问题尽管在代码中通过使用synchronized关键字和双重检查锁定机制来解决线程安全性问题但在某些特定情况下可能会发生失效的情况例如编译器优化问题。实现相对复杂双重检查锁定机制的实现相对复杂容易出错并且对于不熟悉该机制的开发人员来说难以理解和维护。
饿汉式单例模式 优点
简单直观实现起来简单不存在线程安全问题。线程安全由于实例在类加载时就创建因此不存在多线程环境下的线程安全问题。
缺点
资源浪费在类加载时就创建实例对象可能会导致资源的浪费特别是在实例的创建过程中需要执行耗时操作或占用大量资源的情况下。无法实现延迟加载由于实例在类加载时就被创建无法实现按需创建实例的延迟加载需求。
总结 懒汉式单例模式具有延迟加载和线程安全的优点但也存在一些线程安全问题和实现复杂的缺点。饿汉式单例模式简单直观线程安全但可能存在资源浪费和无法实现延迟加载的缺点。在选择单例模式实现方式时需要根据具体需求和场景进行权衡和选择。