百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

你敢说自己了解单例模式? 单例模式的两种实现方法

yuyutoo 2024-10-12 01:23 6 浏览 0 评论

你敢说自己了解单例模式?一、背景

最近在学习设计模式,在看到单例模式的时候,我一开始以为直接很了解单例模式了,实现起来也很简单,但是实际上单例模式有着好几个变种,并且多线程中涉及到线程安全问题,那么本文我们就来好好聊聊单例模式,说一下经典三种实现方式:饿汉式、懒汉式、登记式。并且解决掉多线程中可能出现的线程安全问题。

二、基本概念

1.为什么要使用单例模式?

在我们日常的工作中,很多对象通常占用非常重要的系统资源,比如:IO处理,数据库操作等,那我们必须要限制这些对象只有且始终使用一个公用的实例,即单例。

2.单例模式的实现方式

  • 构造函数私有化,防止其他类生成唯一公用实例外的实例。且
  • 单例类应该被定义为final,也就是说单例类不能被继承,因为如果允许继承那子类就都可以创建实例,违背了类唯一实例的初衷。
  • 类中一个静态变量来保存单实例的引用。
  • 一个共有的静态方法来获取单实例的引用。

3.单例模式的UML类图

4.单例模式的经典实现方式

  • 饿汉式:一开始就创建好实例,每次调用直接返回,经典的“拿空间换时间”。
  • 懒汉式:延迟加载,第一次调用的时候才加载,然后返回,以后的每次的调用就直接返回。经典“拿时间换空间”,多线程环境下要注意解决线程安全的问题。
  • 登记式:对一组单例模式进行的维护,主要是在数量上的扩展,通过线程安全的map把单例存进去,这样在调用时,先判断该单例是否已经创建,是的话直接返回,不是的话创建一个登记到map中,再返回。

三、饿汉式---代码实现

1.单例类

package com.hafiz.designPattern.singleton;
/**
 * Desc: 单例模式-饿汉式
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton1 {
 // 创建全局静态变量,保证只有一个实例
 private static volatile Singleton1 instance = new Singleton1();
 private Singleton1() {
 // 构造函数私有化
 System.out.println("--调用饿汉式单例模式的构造函数--");
 }
 public static Singleton1 getInstance() {
 System.out.println("--调用饿汉式单例模式的静态方法返回实例--");
 return instance;
 }
}

2.测试类

public class DesignPatternTest {
 @Test
 public void testSingleton1() {
 System.out.println("-----------------测试饿汉式单例模式开始--------------");
 Singleton1 instance1 = Singleton1.getInstance();
 System.out.println("第二次获取实例");
 Singleton1 instance2 = Singleton1.getInstance();
 System.out.println("instance1和instance2是否为同一实例?" + (instance1 == instance2));
 System.out.println("-----------------测试饿汉式单例模式结束--------------");
 }
}

3.测试结果

四、懒汉式---代码实现

1.单例类

package com.hafiz.designPattern.singleton;
/**
 * Desc:单例模式-懒汉式
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton2 {
 // 创建全局静态变量,保证只有一个实例
 private static Singleton2 instance = null;
 // 构造函数私有化
 private Singleton2() {
 System.out.println("--调用懒汉式单例模式的构造方法--");
 }
 public static Singleton2 getInstance() {
 System.out.println("--调用懒汉式单例模式获取实例--");
 if (instance == null) {
 System.out.println("--懒汉式单例实例未创建,先创建再返回--");
 instance = new Singleton2();
 }
 return instance;
 }
}

2.测试类

public class DesignPatternTest {
 @Test
 public void testSingleton2() {
 System.out.println("-----------------测试懒汉式单例模式开始--------------");
 Singleton2 instance1 = Singleton2.getInstance();
 System.out.println("第二次获取实例");
 Singleton2 instance2 = Singleton2.getInstance();
 System.out.println("instance1和instance2是否为同一实例?" + (instance1 == instance2));
 System.out.println("-----------------测试懒汉式单例模式结束--------------");
 }
}

3.测试结果

细心的同学已经发现,这种实现方式,在多线程的环境中,是有线程安全安全问题的,有可能两个或多个线程判断instance都为null,然后创建了好几遍实例,不符合单例的思想,我们可以对它进行改进。

五、改进懒汉式1---代码实现

原理:使用JDK的synchronized同步代码块来解决懒汉式线程安全问题。

1.单例类

package com.hafiz.designPattern.singleton;
/**
 * Desc:单例模式-懒汉式
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton2 {
 // 创建全局静态变量,保证只有一个实例
 private static Singleton2 instance = null;
 // 构造函数私有化
 private Singleton2() {
 System.out.println("--调用懒汉式单例模式的构造方法--");
 }
 public static Singleton2 getInstance() {
 System.out.println("--调用懒汉式单例模式获取实例--");
     if (instance != null) {
        System.out.println("--懒汉式单例实例已经创建,直接返回--");
  return instance;
     }
 synchronized (Singleton2.class) {
    if (instance == null) {
   System.out.println("--懒汉式单例实例未创建,先创建再返回--");
   instance = new Singleton2();
    }
 }
 return instance;
 }
} 

2.测试结果

六、改进懒汉式2---代码实现

原理:使用JVM隐含的同步和类级内部类来解决,JVM隐含的同步解决了多线程情况下线程安全的问题,类级内部类解决只有使用的时候才加载(延迟加载)的问题。

1.JVM隐含的同步有哪些?

  • 静态初始化器(在静态字段上或static{}静态代码块的初始化器)初始化数据时
  • 访问final字段时
  • 在创建线程之前创建对象时
  • 线程可以看见它将要处理的对象时

2.什么是类级内部类?

  • 有static修饰的成员式内部类。没有static修饰的成员式内部类叫对象级内部类。
  • 类级内部类相当于其外部类的static成分,他的对象与外部类对象间不存在依赖关系,因此可直接创建,而对象级内部类的实例,是绑定在外部对象实例中的。
  • 类级内部类中,可以定义静态的方法。在静态的方法中只能够引用外部类的中的静态成员方法或者成员变量
  • 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载

3.单例类

package com.hafiz.designPattern.singleton;
/**
 * Desc:单例模式-改进懒汉式
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton3 {
 private static class Singleton4 {
 private static Singleton3 instance;
 static {
 System.out.println("--类级内部类被加载--");
 instance = new Singleton3();
 }
 private Singleton4() {
 System.out.println("--调用类级内部类的构造函数--");
 }
 }
 private Singleton3() {
 System.out.println("--调用构造函数--");
 }
 public static Singleton3 getInstance() {
 System.out.println("--开始调用共有方法返回实例--");
 Singleton3 instance;
 System.out.println("---------------------------");
 instance = Singleton4.instance;
 System.out.println("返回单例");
 return instance;
 }
}

4.测试类

package com.hafiz.www;
import com.hafiz.designPattern.observer.ConcreteObserver;
import com.hafiz.designPattern.observer.ConcreteSubject;
import com.hafiz.designPattern.singleton.Singleton1;
import com.hafiz.designPattern.singleton.Singleton2;
import com.hafiz.designPattern.singleton.Singleton3;
import com.hafiz.designPattern.singleton.Singleton4;
import com.hafiz.designPattern.singleton.Singleton4Child1;
import com.hafiz.designPattern.singleton.SingletonChild2;
import org.junit.Test;
/**
 * Desc:设计模式demo单元测试类
 * Created by hafiz.zhang on 2017/7/27.
 */
public class DesignPatternTest {
 @Test
 public void testSingleton3() {
 System.out.println("-----------------测试改进懒汉式单例模式开始--------------");
 Singleton3 instance1 = Singleton3.getInstance();
 System.out.println("第二次获取实例");
 Singleton3 instance2 = Singleton3.getInstance();
 System.out.println("instance1和instance2是否为同一实例?" + (instance1 == instance2));
 System.out.println("-----------------测试改进懒汉式单例模式结束--------------");
 }
}

5.测试结果

七、登记式--代码实现

1.基类

package com.hafiz.designPattern.singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * Desc: 单例模式-登记式
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton4 {
 private static Map<String, Singleton4> map = new ConcurrentHashMap<>();
 protected Singleton4() {
 System.out.println("--私有化构造函数被调用--");
 }
 public static Singleton4 getInstance(String name) {
 if (name == null) {
 name = Singleton4.class.getName();
 System.out.println("--name为空,默认赋值为:--" + Singleton4.class.getName());
 }
 if (map.get(name) != null) {
 System.out.println("name对应的值存在,直接返回");
 return map.get(name);
 }
 System.out.println("name对应的值不存在,先创建,再返回");
 try {
 Singleton4 result = (Singleton4)Class.forName(name).newInstance();
 map.put(name, result);
 return result;
 } catch (InstantiationException e) {
 e.printStackTrace();
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 return null;
 }
 public Map<String, Singleton4> getMap() {
 return map;
 }
}

2.子类1

package com.hafiz.designPattern.singleton;
/**
 * Desc:
 * Created by hafiz.zhang on 2017/9/26.
 */
public class Singleton4Child1 extends Singleton4 {
 public static Singleton4Child1 getInstance() {
 return (Singleton4Child1) Singleton4.getInstance("com.hafiz.designPattern.singleton.Singleton4Child1");
 }
}

3.子类2

package com.hafiz.designPattern.singleton;
/**
 * Desc:
 * Created by hafiz.zhang on 2017/9/26.
 */
public class SingletonChild2 extends Singleton4 {
 public static SingletonChild2 getInstance() {
 return (SingletonChild2) Singleton4.getInstance("com.hafiz.designPattern.singleton.SingletonChild2");
 }
}

4.测试类

public class DesignPatternTest {
 @Test
 public void testSingleton4() {
 System.out.println("-----------------测试登记式单例模式开始--------------");
 System.out.println("第一次取得实例");
 Singleton4 instance1 = Singleton4.getInstance(null);
 System.out.println("res:" + instance1);
 System.out.println("第二次获取实例");
 Singleton4Child1 instance2 = Singleton4Child1.getInstance();
 System.out.println("res:" + instance2);
 System.out.println("第三次获取实例");
 SingletonChild2 instance3 = SingletonChild2.getInstance();
 System.out.println("res:" + instance3);
 System.out.println("第四次获取实例");
 SingletonChild2 instance4 = new SingletonChild2();
 System.out.println("res:" + instance4);
 System.out.println("输出父类Map中所有的单例");
 Map<String, Singleton4> map = instance1.getMap();
 for (Map.Entry<String, Singleton4> item : map.entrySet()) {
 System.out.println("map-item:" + item.getKey() + "=" + item.getValue());
 }
 System.out.println("instance1和instance2是否为同一实例?" + (instance1 == instance2));
 System.out.println("-----------------测试登记式单例模式结束--------------");
 }
}

5.测试结果

该解决方案的缺点:基类的构造函数对子类公开了(protected),有好的解决方案的博友可以讨论指教~

八、总结

经过本文,我们就搞明白了什么叫单例模式,如何优雅的实现经典的单例模式,如何进行拓展和开发具有线程安全的单例模式。对于我们以后的开发非常有帮助,也让我们更加了解单例模式。

相关推荐

墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人

中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...

网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕

伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...

一课译词:来龙去脉(来龙去脉 的意思解释)

Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...

高考重要考点:range(range高考用法)

range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...

C++20 Ranges:现代范围操作(现代c++白皮书)

1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...

学习VBA,报表做到飞 第二章 数组 2.4 Filter函数

第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...

VBA学习笔记:数组:数组相关函数—Split,Join

Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...

VBA如何自定义序列,学会这些方法,让你工作更轻松

No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...

Excel VBA入门教程1.3 数组基础(vba数组详解)

1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...

远程网络调试工具百宝箱-MobaXterm

MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...

AREX:携程新一代自动化回归测试工具的设计与实现

一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...

Windows、Android、IOS、Web自动化工具选择策略

Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...

python自动化工具:pywinauto(python快速上手 自动化)

简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...

时下最火的 Airtest 如何测试手机 APP?

引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...

【推荐】7个最强Appium替代工具,移动App自动化测试必备!

在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...

取消回复欢迎 发表评论: