【设计模式与体系结构】创建型模式-单例模式
引言
张三和其舍友收假后回到宿舍,并闲聊了起来。张三说:“我昨天吃了家店,菜品很不错。”其舍友也说:“我昨天也在校门口一家新开的店吃了一下,那家新开的店也很不错。”张三说:“我昨天吃的是鸡公煲,你吃的是什么?”其舍友说:“巧了,我吃的也是鸡公煲。”张三说:“学校门口有好几家鸡公煲呢,你吃的是哪一家的?”其舍友说:“就校门口正对面那家。”此时张三一拍大腿:“哦!说了半天,原来我们吃的是同一家店。”
有时候不同的人对同一个事物的描述是会有些许不同的,但是实际讲的其实是同一件事情。比如张三和其舍友吃鸡公煲的故事,不会因为两个人对店的描述不同,就导致他们吃的“同一家店”变成“不同的店”。张三去校门口正对面那家店吃鸡公煲,和其舍友去校门口正对面那家店吃鸡公煲,这两个人去的都会是同一家店。那么这个店呢,就是个唯一确定的店,无论是他们谁去吃,去的都是同一家店,而不会因为描述者的不同而导致店不同。
类似于“去同一家店吃鸡公煲”的事情,在计算机领域有一种设计模式叫做单例模式,指的就是整个程序无论在何处访问,访问到的都是同一个实例。鸡公煲店就相当于单例的实例,张三和其舍友就相当于不同的类,这两个类访问同一个单例,虽然功能不同(相当于二人对店的描述不同),但是访问的仍然是同一个单例(说的仍然是同一家店)。
简介
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一对象的方法,可以直接访问,不需要实例化该对象。
单例模式的角色
- 单例类:创建一个实例的类
- 访问类:访问单例类
单例模式的类型
- 饿汉式:类加载就会导致该单例对象被创建
- 懒汉式:类加载不会导致该单例对象被创建,而是首次使用时才创建
单例模式的特点
- 唯一性:单例类在整个应用程序只会存在一个实例
- 全局访问:通过一个静态方法或属性,可以在整个程序的任何地方方便地获取到单例的实例
正文
饿汉式-静态变量方式
以张三与其舍友吃鸡公煲的案例,写一个饿汉式-静态变量方式的程序(毕竟这两人一见面就聊吃的,多半都是饿汉)。
- 创建一个实体类People.java
public class People { String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void order() {//点菜方法 System.out.println(getName() + "点了一份鸡公煲。"); RestaurantSingleton restaurantSingleton = RestaurantSingleton.getInstance(); System.out.println(getName() + "去的饭店是" + restaurantSingleton.hashCode()); } }
- 创建一个饭店单例类RestaurantSingleton.java
public class RestaurantSingleton { //私有构造方法 private RestaurantSingleton() { } //在本类中创建本类对象 private final static RestaurantSingleton restaurantSingleton = new RestaurantSingleton(); //提供一个公共的访问方法,供外界访问 public static RestaurantSingleton getInstance() { return restaurantSingleton; } }
- 创建两个用户访问类
People zhangsan = new People(); zhangsan.setName("张三"); zhangsan.order(); People lisi = new People(); lisi.setName("李四"); lisi.order();
- 运行结果如下
说明虽然饭店单例类RestaurantSingleton.java的调用者不同,但是获取到的单例实例是同一个。
饿汉式-静态代码块方式
public class RestaurantSingleton { //私有构造方法 private RestaurantSingleton() { } //声明 RestaurantSingleton 类型的变量 private final static RestaurantSingleton restaurantSingleton; //在静态代码块中初始化 restaurantSingleton 变量 static { restaurantSingleton = new RestaurantSingleton(); } //提供一个公共的访问方法,供外界访问 public static RestaurantSingleton getInstance() { return restaurantSingleton; } }
懒汉式-线程不安全方式
想开鸡公煲店的老板,当然是想把店开在有顾客的地方,如果开个完全没有顾客的店,那显然他就做了场赔本生意。
对于单例模式饿汉式,由于类的实例是静态的,因此会一直存在于内存中。若长时间不使用,会造成内存的浪费。那么将单例的实例化延迟到被使用时,会更节约系统资源,于是引入了懒汉式单例模式:
public class RestaurantSingleton { //私有构造方法 private RestaurantSingleton() { } //声明 RestaurantSingleton 类型的变量 private static RestaurantSingleton restaurantSingleton = null;//仅声明,未赋值 //提供一个公共的访问方法,供外界访问 public static RestaurantSingleton getInstance() { if (restaurantSingleton == null) {//若未赋值过则进行赋值,否则直接返回 restaurantSingleton = new RestaurantSingleton(); } return restaurantSingleton; } }
上述代码不会在类加载时就创建一个静态变量,而是在系统真正调用这个变量时才进行创建,会使得使用的效率更高。但是同时也引入了新的问题:
有两个老板,在相同的时间段发现了同一块空地,这两个老板都想在这块空地开一家鸡公煲店,于是各自回去准备建造设施。后面当其中一个老板准备好家伙要来盖店的时候,却发现早已被另一个老板抢占先机盖了店。于是二者争执不下,都说自己先发现的,自己来的时候还没有被开发。这说的就是线程不安全。
懒汉式-线程安全方式
于是一个老板想到了一个办法,要去探索一块地之前,先把方圆十里全用铁栅栏围起来,然后自己再安心地去看看有没有空地,有空地再建起来。相当于对于一个程序,可以使用关键字 synchronized 修饰函数,使得不同对象对同一个函数的访问是串行的。
public class RestaurantSingleton { //私有构造方法 private RestaurantSingleton() { } //声明 RestaurantSingleton 类型的变量 private static RestaurantSingleton restaurantSingleton = null;//仅声明,未赋值 //提供一个公共的访问方法,供外界访问 //synchronized 关键字可以使得对该函数的访问是串行的 public static synchronized RestaurantSingleton getInstance() { if (restaurantSingleton == null) {//若未赋值过则进行赋值,否则直接返回 restaurantSingleton = new RestaurantSingleton(); } return restaurantSingleton; } }
懒汉式-双重检查锁方式
有个老板每次把方圆十里的地都围起来了,但是每次去发现空地的时候,都发现那些空地早就被抢占先机了。于是老板就很苦恼,每次围地都很费劲,但是最后却发现没有自己可以建鸡公煲店的地方。于是他想了一个法子:先去找到空地,如果有空地再去围方圆十里,围完以后再去检查一下空地是不是还是空着的,如果是就可以安心的建鸡公煲店了。这就相当于程序中的双重检查锁方式。
public class RestaurantSingleton { //私有构造方法 private RestaurantSingleton() { } //声明 RestaurantSingleton 类型的变量 private static RestaurantSingleton restaurantSingleton = null;//仅声明,未赋值 //提供一个公共的访问方法,供外界访问 public static RestaurantSingleton getInstance() { //第一次判断,若 restaurantSingleton 不为 null,则不进入抢锁阶段 if (restaurantSingleton == null) { //进行抢锁操作 synchronized (RestaurantSingleton.class) { //抢到锁后,再次判断 restaurantSingleton 是否为 null,若是则赋值,否则直接返回 if (restaurantSingleton == null) { restaurantSingleton = new RestaurantSingleton(); } } } return restaurantSingleton; } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 本地部署 DeepSeek:小白也能轻松搞定!
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 从 Windows Forms 到微服务的经验教训
· 李飞飞的50美金比肩DeepSeek把CEO忽悠瘸了,倒霉的却是程序员
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee