设计模式连更(2)-单例模式
单例模式
单例模式是一种创建型模式,让你确保一个类只有一个实例对象,并提供一个访问该节点的全局变量
实现方案
所有单例的实现都包括两个相同的步骤
在类中添加一个私有静态成员变量用于保存单例实例
声明一个公有静态构建方法用于获取单例实例
在静态方法中实现延迟初始化
将默认构造函数设为私有,防止其他对象使用单例类的new运算符
检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用
伪代码
数据库连接
// 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处 // 都能访问相同的数据库连接实例。 class Database is // 保存单例实例的成员变量必须被声明为静态类型。 private static field instance: Database // 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构 // 造方法。 private constructor Database() is // 部分初始化代码(例如到数据库服务器的实际连接)。 // ... // 用于控制对单例实例的访问权限的静态方法。 public static method getInstance() is if (Database.instance == null) then acquireThreadLock() and then // 确保在该线程等待解锁时,其他线程没有初始化该实例。 if (Database.instance == null) then Database.instance = new Database() return Database.instance // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。 public method query(sql) is // 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以 // 在这里添加限流或缓冲逻辑。 // ... class Application is method main() is Database foo = Database.getInstance() foo.query("SELECT ...") // ... Database bar = Database.getInstance() bar.query("SELECT ...") // 变量 `bar` 和 `foo` 中将包含同一个对象。复制代码
适用场景
如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式
单例模式禁止通过除特殊构建方法意外的任何方式来创建自身类的对象。该方法可以创建一个新的对象,的那如果该对象已经被创建,则返回已有的对象
如果你需要更加严格地控制全局变量,则可以使用单例模式
单例模式和全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过热河方式替换缓存的实例
立即加载/饿汉模式
立即加载是指使用类的时候已经将对象创建完毕,常见的实现方法,是直接用new实例化。立即加载有”着急“”迫切“的意思,所以也称为”饿汉模式“
在立即加载中,调用方法前,实例已经被工厂创建了
public class MyObject { //立即加载方式 private static MyObject myObject = new MyObject(); private MyObject(){ } public static MyObject getInstance(){ //实例化 return myObject; } }复制代码
延迟加载/懒汉模式
延迟加载是指调用get()方法时实例才被工厂创建,常见的实现方法是在get()中进行new实例化
public class MyObject { //立即加载方式 private static MyObject myObject; private MyObject(){ } public static MyObject getInstance(){ if (myObject != null){ }else { myObject = new MyObject(); } return myObject; } }复制代码
但是这种情况在多线程环境中会出现取出多个实例的情况,这和单例模式的初衷是违背的 解决方案
声明synchronized关键字,但是运行效率非常低
public class MyObject { //立即加载方式 private static MyObject myObject; private MyObject() { } //设置同步方法 synchronized public static MyObject getInstance() { try { if (myObject != null) { } else { Thread.sleep(3000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }复制代码
尝试同步代码块,但是效率还是非常低
public class MyObject { //立即加载方式 private static MyObject myObject; private MyObject() { } //设置同步方法 public static MyObject getInstance() { try { synchronized (MyObject.class) { if (myObject != null) { } else { Thread.sleep(3000); myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }复制代码
针对某些重要代码进行单独同步,效率虽然提高了,但是在多线程的情况下还是无法解决得到同一个实例对象的结果
public class MyObject { //立即加载方式 private static MyObject myObject; private MyObject() { } //设置同步方法 public static MyObject getInstance() { try { if (myObject != null) { } else { Thread.sleep(3000); //部分上锁 synchronized (MyObject.class) { myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }复制代码
DCL机制 DCL(Double-Check Locking)双重锁机制
public class MyObject { //立即加载方式 private volatile static MyObject myObject; private MyObject() { } //设置同步方法 public static MyObject getInstance() { try { if (myObject != null) { } else { Thread.sleep(3000); //部分上锁 synchronized (MyObject.class) { if (myObject == null) { myObject = new MyObject(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }复制代码
通过volatile修改变量myObject使该变量在多个线程间达到可见性,另外也禁止了myObject=new MyObject()代码重排序,因为myObject=new MyObject()的执行顺序为
分配空间
初始化对象
设置instance指向刚分配的内存地址
所以要禁止重排序
DCL是大多数多线程结合单例模式使用的解决方案
作者:谁是Lehends啊
链接:https://juejin.cn/post/7025174923299520549