当前位置: 首页 > news >正文

设计模式 - 单例模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

单例模式共有8种方式:

  • 饿汉式(静态常量),可以使用
  • 饿汉式(静态代码块),可以使用
  • 懒汉式(线程不安全),不推荐使用
  • 懒汉式(同步方法),线程安全,可以使用,但是不推荐
  • 懒汉时(同步代码块),想解决线程安全问题,但是这是一种错误的方法,没有解决线程安全问题。不能使用
  • 双重检查,推荐使用
  • 静态内部类,推荐使用
  • 枚举,推荐使用

1 饿汉式

饿汉式,类很饿,饥不择食,一上来就会创建对象实例。饿汉式不存在线程安全问题。

1.1 静态常量

步骤:

  • 构造器私有化
  • 类的内部创建对象
  • 向外暴露一个静态的公共方法getInstance

代码实现:

public class User {// 静态常量private static final User user = new User();// 私有化构造器,方式外部通过new来创建对象private User(){}// 对外暴露的方法public static User getInstance() {return user;}
}

优点:

  • 写法简单,在类装载的时候就完成了实例化,避免了线程同步问题。

缺点:

  • 在类装载时就完成类实例化,没有达到懒加载(Lazy Loading)的效果。如果从未使用过这个实例,则会造成内存的浪费。

总结:这种单例模式可用,但是可能造成内存浪费。

1.2 静态代码块

public class Book {private static Book book;// 静态代码块static {book = new Book();}// 私有化构造方法,防止外部通过new的方式创建对象private Book() {}// 对外暴露获取实例的方法public static Book getInstance() {return book;}
}

该方法和静态常量方法类似。优点缺点与静态常量方法相同。

2 懒汉时

2.1 线程不安全

public class Dog {private static Dog dog;// 私有化构造方法,防止外部通过new的方式创建对象private Dog() {}// 对外暴露获取实例的方法,当调用该方式时,才会创建实例对象public static Dog getInstance() {if (dog==null){dog = new Dog();}return dog;}
}

优点:

  • 起到了懒加载的效果,但是只能在单线程下使用。

缺点:

  • 如果在多线程下,一个线程进行了if(dog==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,所以在多线程环境下不可使用这种方式。

总结:在实际开发中,不要使用这种方式!

2.2 同步方法-线程安全

public class Cat {private static Cat cat;// 私有化构造方法,防止外部通过new的方式创建对象private Cat() {}// 对外暴露获取实例的方法,当调用该方式时,才会创建实例对象。使用synchronized关键字保障线程安全public static synchronized Cat getInstance(){if(cat == null){cat = new Cat();}return cat;}
}

优点:

  • 解决了线程不安全问题。

缺点:

  • 效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面想获得该类实例,直接return就行了。方法进行同步效率太低。

结论:在实际开发中,不推荐使用这种方式!

2.3 同步代码块-不能解决线程安全

在实际开发中,有人尝试解决线程安全问题,但是又想解决同步方法带来的效率低的问题,所以使用了下面的方法。将cat = new Cat()创建新实例的代码放入了一个同步代码块中。

但是这种方法其实并没有解决线程安全问题。因为可能会多个线程通过if(cat == null)判断,也会造成多次创建实例的情况。

public class Cat {private static Cat cat;// 私有化构造方法,防止外部通过new的方式创建对象private Cat() {}// 对外暴露获取实例的方法,当调用该方式时,才会创建实例对象。public static Cat getInstance(){if(cat == null){// 同步代码块synchronized (Cat.class){cat = new Cat();}}return cat;}
}

这是一种错误写法!!

2.4 双重检查

volatile关键字可以让修改值立即更新到主存。在对象的构造过程中,volatile 可以确保在对象引用被赋值给其他线程之前,对象的构造过程已经完成。

如果不加volatile关键字,一个线程修改了 pig 的值,其他线程可能看不到这个修改,导致可能创建多个实例。

public class Pig {private static volatile Pig pig;// 私有化构造方法,防止外部通过new的方式创建对象private Pig() {}// 对外暴露获取实例的方法,当调用该方式时,才会创建实例对象。public static Pig getInstance() {if (pig == null) {synchronized (Pig.class) {// 再次检查 if (pig == null) {pig = new Pig();}}}return pig;}
}

双重检查这种方式,在保证线程安全的同时,也具有较高的效率。在实际开发中,推荐使用这种方法。

2.5 静态内部类

Tigger类进行装载时,TiggerInstance静态内部类是不会被装载的。

当我们调用getInstance方法时,会装载TiggerInstance内部类,同时静态内部类的静态常量INSTANCE对象会被创建。

public class Tigger {// 私有化构造方法,防止外部通过new的方式创建对象private Tigger(){}// 静态内部类private static class TiggerInstance {private static final Tigger INSTANCE = new Tigger();}// 对外暴露获取实例的方法,当调用该方式时,才会创建实例对象。private static Tigger getInstance(){return TiggerInstance.INSTANCE;}
}

这种方式采用类加载的机制来保证初始化实例时只有一个线程。

这种方法保证了线程安全,效率高。推荐使用!

2.6 枚举

public enum Fish {INSTANCE;public void sayOK(){System.out.println("ok");}
}

借助枚举实现单利模式,可以避免多线程同步问题,而且还能防止反序列化重新创建新的对象。推荐使用。

3 单例模式注意事项

单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

当想实例化一个单例类的时候,必须要使用相应的获取对象的方法,而不是直接用new

单例模式使用的场景:

  • 需要频繁的进行创建和销毁的对象
  • 创建对象时耗时过多或耗费资源过多(即重量级对象),但又经常用到的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象,比如数据源、session工厂等

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 密码学基本理论
  • pnpm -C 什么意思
  • 量化投资策略与技术学习PART2:量化选股之风格轮动
  • Docker深入讲解
  • IOS企业IPA软件证书 苹果签名证书 有效期到2026年
  • VMware ESXi学习笔记
  • 【网络安全学习】SQL注入02:使用sqlmap进行注入
  • WPS宏实现对表格选中区域数据进行遍历读取及动态赋值
  • vs2022 开发vue带后端
  • 代码整洁之道-如何写好注释
  • 在Kylin服务器安装PostgreSQL16数据库
  • 【深度学习|目标跟踪】快速入门卡尔曼滤波!
  • 笔记本CPU天梯图(2024年8月),含AMD/骁龙等新CPU
  • 北京青蓝智慧科技:160个项目通过“数据要素×”大赛湖北分赛初赛
  • uniapp 开发公众号 h5(openid,微信支付,订阅通知)
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【mysql】环境安装、服务启动、密码设置
  • create-react-app做的留言板
  • Debian下无root权限使用Python访问Oracle
  • Java 网络编程(2):UDP 的使用
  • Java方法详解
  • Making An Indicator With Pure CSS
  • miaov-React 最佳入门
  • Object.assign方法不能实现深复制
  • October CMS - 快速入门 9 Images And Galleries
  • oschina
  • Redux 中间件分析
  • 初识 beanstalkd
  • 大主子表关联的性能优化方法
  • 欢迎参加第二届中国游戏开发者大会
  • 盘点那些不知名却常用的 Git 操作
  • 线上 python http server profile 实践
  • 小程序01:wepy框架整合iview webapp UI
  • 携程小程序初体验
  • 一些css基础学习笔记
  • 异步
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • ​如何在iOS手机上查看应用日志
  • # 数据结构
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • #数据结构 笔记三
  • #预处理和函数的对比以及条件编译
  • ${factoryList }后面有空格不影响
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (windows2012共享文件夹和防火墙设置
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (二)JAVA使用POI操作excel
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .NET 8.0 中有哪些新的变化?
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes