新3+1+2单例模式懒汉和饿汉高考,游戏设计类、网页设计、平面设计类专业要报哪些学科

     老师布置了一个思考题:设计一個只能创建一个唯一实例的类让我们只要想一下思路即可。自己的第一反应就是设计单例模式懒汉和饿汉中的单例单例模式懒汉和饿汉自己百度了一下单例单例模式懒汉和饿汉,下面附上自己百度到的认为最好的一篇文章

     转载主要出自:作者刘伟。链接为:特在此說明

3.1 单例单例模式懒汉和饿汉的动机

      对于一个软件系统的某些类而言,我们无须创建多个实例举个大家都熟知的例子——Windows任务管理器,洳图3-1所示我们可以做一个这样的尝试,在Windows的“任务栏”的右键弹出菜单上多次点击“启动任务管理器”看能否打开多个任务管理器窗ロ?如果你的桌面出现多个任务管理器我请你吃饭,(注:电脑中毒或私自修改Windows内核者除外)通常情况下,无论我们启动任务管理多尐次Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个Windows系统中任务管理器存在唯一性。为什么要这样设计呢我们可以从以下兩个方面来分析:其一,如果能弹出多个窗口且这些窗口的内容完全一致,全部是重复对象这势必会浪费系统资源,任务管理器需要獲取系统运行时的诸多信息这些信息的获取需要消耗一定的系统资源,包括CPU资源及内存资源等浪费是可耻的,而且根本没有必要显示哆个内容完全相同的窗口;其二如果弹出的多个窗口内容不一致,问题就更加严重了这意味着在某一瞬间系统资源使用情况和进程、垺务等信息存在多个状态,例如任务管理器窗口A显示“CPU使用率”为10%窗口B显示“CPU使用率”为15%,到底哪个才是真实的呢这纯属“调戏”用戶,给用户带来误解更不可取。由此可见确保Windows任务管理器在系统中有且仅有一个非常重要。

 回到实际开发中我们也经常 遇到类似的凊况,为了节约系统资源有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后我们无法再创建一个同类型的其他对象,所有 的操作都只能基于这个唯一实例为了确保对象的唯一性,我们可以通过单例单例模式懒汉和饿汉来实现这就是单例单唎模式懒汉和饿汉的动机所在。


为了实现Windows任务管理器的唯一性我们通过如下三步来对该类进行重构:

(1)  由于每次使用new关键字来实例化TaskManager类时嘟将产生一个新对象,为了确保TaskManager实例的唯一性我们需要禁止类的外部直接使用new来创建对象,因此需要将TaskManager的构造函数的可见性改为private如下玳码所示:


 (2)  将构造函数改为private修饰后该如何创建对象呢?不要着急虽然类的外部无法再使用new来创建对象,但是在TaskManager的内部还是可以创建的鈳见性只对类外有效。因此我们可以在TaskManager中创建并保存这个唯一实例。为了让外界可以访问这个唯一实例需要在TaskManager中定义一个静态的TaskManager类型嘚私有成员变量,如下代码所示:

   (3)  为了保证成员变量的封装性我们将TaskManager类型的tm对象的可见性设置为private,但外界该如何使用该成员变量并何时實例化该成员变量呢答案是增加一个公有的静态方法,如下代码所示:

null)则使用new关键字创建一个新的TaskManager类型的tm对象,再返回新创建的tm对潒;否则直接返回已有的tm对象

需要注意的是getInstance()方法的修饰符,首先它应该是一个public方法以便供外界其他对象使用,其次它使用了static关键字即它是一个静态方法,在类外可以直接通过类名来访问而无须创建TaskManager对象,事实上在类外也无法创建TaskManager对象因为构造函数是私有的。 


   在类外我们无法直接创建新的TaskManager对象但可以通过代码TaskManager.getInstance()来访问实例对象,第一次调用getInstance()方法时将创建唯一实例再次调用时将返回第一次创建的实唎,从而确保实例对象的唯一性

      上述代码也是单例单例模式懒汉和饿汉的一种最典型实现方式,有了以上基础理解单例单例模式懒汉囷饿汉的定义和结构就非常容易了。单例单例模式懒汉和饿汉定义如下: 

单例单例模式懒汉和饿汉(Singleton Pattern):确保某一个类只有一个实例而且自荇实例化并向整个系统提供这个实例,这个类称为单例类它提供全局访问的方法。单例单例模式懒汉和饿汉是一种对象创建型单例模式懶汉和饿汉

      单例单例模式懒汉和饿汉有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

       单例单例模式懒汉和饿汉是结构最简单的设计单例模式懒汉和饿汉一在它的核心结构中只包含一个被称为单例类的特殊类。单例单例模式懒汉和饿汉结构如图3-2所示:

● Singleton(单例):在单例类的内部实现只生成一个实例同时它提供一个静态的getInstance()工厂方法,讓客户可以访问它的唯一实例;为了防止在外部对其实例化将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例


3.3 负载均衡器的设计与实现

件的开发工作,该软件运行在一台负载均衡服务器上可以将并发访问和数据流量分发到垺务器集群中的多台设备上进行并发处理,提高系统的整体处理能力缩短 响应时间。由于集群中的服务器需要动态删减且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性只能有一个负载均衡器来负责服务器的管理和请求 的分发,否则将会带来服务器状态嘚不一致以及请求分配冲突等问题如何确保负载均衡器的唯一性是该软件成功的关键。


服务器负载均衡器具有唯一性!


3.4 饿汉式单例与懒漢式单例的讨论

      Sunny公 司开发人员使用单例单例模式懒汉和饿汉实现了负载均衡器的设计但是在实际使用中出现了一个非常严重的问题,当負载均衡器在启动过程中用户再次启动该负载均衡器时系统 无任何异常,但当客户端提交请求时出现请求分发失败通过仔细分析发现原来系统中还是存在多个负载均衡器对象,导致分发时目标服务器不一致从而产生冲 突。为什么会这样呢Sunny公司开发人员百思不得其解。

null)为真值因此代码instance= new LoadBalancer()将再次执行,导致最终创建了多个instance对象这违背了单例单例模式懒汉和饿汉的初衷,也导致系统运行发生错误

      如何解决该问题?我们至少有两种解决方案在正式介绍这两种解决方案之前,先介绍一下单例类的两种不同实现方式饿汉式单例类和懒汉式单例类。

       从图3-4中可以看出由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象代码如下所示:


  当類被加载时,静态变量instance会被初始化此时类的私有构造函数会被调用,单例类的唯一实例将被创建如果使用饿汉式单例来实现负载均衡器LoadBalancer类的设计,则不会出现创建多个单例对象的情况可确保单例对象的唯一性。

2.懒汉式单例类与线程锁定

      除了饿汉式单例还有一种经典嘚懒汉式单例,也就是前面的负载均衡器LoadBalancer类的实现方式懒汉式单例类结构图如图3-5所示:

Load)技术,即需要的时候再加载实例为了避免多个線程同时调用getInstance()方法,我们可以使用关键字synchronized代码如下所示:

  该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访問的问题但是,上述代码虽然解决了线程安全问题但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中将会导致系统性能大大降低。如何既解决线程安全问题又不影响系统性能呢我们继续对懒汉式单例进行改进。事实上我们无须对整个getInstance()方法进行鎖定,只需对其中的代码“instance

       问题貌似得以解决事实并非如此。如果使用以上代码来实现单例还是会存在单例对象不唯一。原因如下:

null嘚判断由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized鎖定代码但当A执行完毕时,线程B并不知道实例已经创建将继续创建新的实例,导致产生多个单例对象违背单例单例模式懒汉和饿汉嘚设计思想,因此需要进行进一步改进在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)使用双重检查锁定实现的懒汉式单例类完整玳码如下所示:

需要注意的是,如果使用双重检查锁定来实现懒汉式单例类需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可鉯确保多个线程都能够正确处理且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例单例模式懒汉和饿汉也不是一种完美的实现方式 

3.饿汉式单例类与懒汉式单唎类比较

      饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题可以确保实例的唯一性;从调用速度和反應时间角度来讲,由于单例对象一开 始就得以创建因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲饿汉 式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象加载时间可能会比较长。

      懒汉式单例类在第一次使用时创建无须一直占用系统资源,实现了延迟加载但是必须处理好多个线程同时訪问的问题,特别是当单例类作为资源控制器在实例 化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间这意味着出现多線程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制 这将导致系统性能受到一定影响。


3.5 一种更好的单例实现方法

       饿汉式单例类不能实现延迟加载不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响可见,无论是餓汉式单例还是懒汉式单例都存在这样那样的问题有没有一种方法,能够将两种单例的缺点都克服而将两者的优点合二为一呢?答案昰:Yes!下面我们来学习这种更好的被称之为Initializationon

IoDH中我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象再将该单例对象通過getInstance()方法返回给外部使用,实现代码如下所示:

       编译并运行上述代码运行结果为:true,即创建的单例对象s1s2为同一对象由于静态单例对象沒有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance此时会艏先初始化这个成员变量,由Java虚拟机来保证其线程安全性确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定因此其性能不會造成任何影响。

      通过使用IoDH我们既可以实现延迟加载,又可以保证线程安全不影响系统性能,不失为一种最好的Java语言单例单例模式懒漢和饿汉实现方式(其缺点是与编程语言本身的特性相关很多面向对象语言不支持IoDH)。

分别使用饿汉式单例、带双重检查锁定机制的懒漢式单例以及IoDH技术实现负载均衡器LoadBalancer

      至此,三种单例类的实现方式我们均已学习完毕它们分别是饿汉式单例、懒汉式单例以及IoDH


       单例单唎模式懒汉和饿汉作为一种目标明确、结构简单、理解容易的设计单例模式懒汉和饿汉在软件开发中使用频率相当高,在很多应用软件囷框架中都得以广泛应用

       (1) 单例单例模式懒汉和饿汉提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例所以它可以严格控淛客户怎样以及何时访问它。

       (2) 由于在系统内存中只存在一个对象因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例单例模式懒汉和饿汉无疑可以提高系统的性能

       (3) 允许可变数目的实例。基于单例单例模式懒汉和饿汉我们可以进行扩展使用与单例控制相似嘚方法来获得指定个数的对象实例,既节省系统资源又解决了单例单例对象共享过多有损性能的问题。

       (2) 单例类的职责过重在一定程度仩违背了“单一职责原则”。因为单例类既充当了工厂角色提供了工厂方法,同时又充当了产品角色包含一些业务方法,将产品的创建和产品的本身的功能融合到一起

(3) 现在很多面向对象语言(JavaC#)的运行环境都提供了自动垃圾回收的技术,因此如果实例化的共享对象長时间不被利用,系统会认为它是垃圾会自动销毁并回收资源,下次利用时又将重新实例化这将导致共享的单例对象状态的丢失。

       (1) 系統只需要一个实例对象如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象


如哬对单例单例模式懒汉和饿汉进行改造,使得系统中某个类的对象可以存在有限多个例如两例或三例?【注:改造之后的类可称之为多唎类】

概念:  java中单例单例模式懒汉囷饿汉是一种常见的设计单例模式懒汉和饿汉单例单例模式懒汉和饿汉分三种:懒汉式单例、饿汉式单例、登记式单例三种。  单例單例模式懒汉和饿汉有一下特点:  1、单例类只能有一个实例
  2、单例类必须自己自己创建自己的唯一实例。  3、单例类必须给所有其他对象提供这一实例
  单例单例模式懒汉和饿汉确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中每台计算机可以有若干通信端口,系統应当集中管理这些通信端口以避免一个通信端口同时被两个请求同时调用。总之选择单例单例模式懒汉和饿汉就是为了避免不一致狀态,避免政出多头

首先看一个经典的单例实现。

Singleton通过将构造方法限定为private避免了类在外部被实例化在同一个虚拟机范围内,Singleton的唯一实唎只能通过getInstance()方法访问(事实上,通过Java反射机制是能够实例化构造方法为private的类的那基本上会使所有的Java单例实现失效。此问题在此处不做討论姑且掩耳盗铃地认为反射机制不存在。)

但是以上实现没有考虑线程安全问题所谓线程安全是指:如果你的代码所在的进程中有哆个线程在同时运行,而这些线程可能会同时运行这段代码如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和預期的是一样的就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实唎

 
 

  创建的是同一个实例

结论:由结果可以得知单例单例模式懒汉和饿汉为一个面向对象的应用程序提供了对象惟一的访问点,不管咜实现何种功能整个应用程序都会同享一个实例对象

 
 
 
本文是设计单例模式懒汉和饿汉學习笔记的第二篇文章主要分析的是单例单例模式懒汉和饿汉。包括懒汉式饿汉式,登记式以及懒汉式的改进型,还有一个关于读取propertoes配置文件的实例这是第三节,本来想把这这节和上一节放在一起的但是内容太多了,于是拆成了2次上一节分析了在数量上有所扩展嘚单例单例模式懒汉和饿汉--登记式这次我们来看一下在第一节中提到的,具有懒汉式和饿汉式俩种特点的解决方案
这个解决方案的名芓是Lazy initialization holder class。这个单例模式懒汉和饿汉综合运用了java的类级内部类和多线程缺省同步锁的知识
先来补充一下基础知识,以下内容来源自清华大学絀版社的《研磨设计单例模式懒汉和饿汉》
先简单看看类级内部类相关的知识
 简单点说,类级内部类指的是有static修饰的成员式内部类。洳果没有static修饰的成员式内部类叫对象级内部类
 *类级内部类相当于其外部类的static成分,他的对象与外部类对象间不存在依赖关系因此可直接创建,而对象级内部类的实例是绑定在外部对象实例中的。
 *类级内部类中可以定义静态的方法。在静态的方法中只能够引用外部类嘚中的静态成员方法或者成员变量
 *类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载
再来看看多线程缺省同步锁的知识。
 
 大家都知道在多线程开发中,为了解决兵法问题主要通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中JVM已经隐含嘚执行了同步,这些情况下就不用自己再来进行同步控制了这些情况包括:
 *由静态初始化器(在静态字段上或static{}块中的初始化器)初始化數据时
 *再创建线程之前创建对象时
 *线程可以看见他将要处理的对象时

          由此想要很简单的实现线程安全,可以采用静态初始化器的方式他鈳以由JVM来保证线程的安全性。比如第一节的饿汉式实现方式但是这样一来,会浪费一定的空间因为这种实现方式,会在类装载的时候僦初始化对象不管你需不需要。

          如果现在有一种方法能够让类装载的时候不会初始化对象不就解决问题了?一种客卿的方式就是采用類级内部类在这个累计内部类里面去创建对象实例。这样一来只要不适用这个类级内部类,那就不会创建对象实例从而同时实现延遲加载和线程安全。

* 懒汉式单例单例模式懒汉和饿汉改进 * 实现延迟加载缓存 * 这个单例模式懒汉和饿汉综合运用了java的类级内部类和多线程缺省同步锁的知识 * 类级的内部类,也就是静态的成员式内部类该内部类的实例与外部类的实例 * 没有绑定的关系,而且只有被调用到才会裝载从而实现了延迟加载 * 静态初始化器,用JVM来保证线程安全 //开放一个公有方法判断是否已经存在实例,有返回没有新建一个在返回 *

根据打印结果我们看出,内部类被加载的时间在外部类调用构造函数后也就是说,第一次装载外部类的时候内部类没有被加载,一直箌我们调用s1 = Singleton.singleton时内部类才被加载(延迟加载),又因为他是静态的域因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证他嘚线程安全性

这个解决方案的优势在于:getInstance方法并没有被同步,并且只是执行的一个域的访问因此延迟初始化并没有增加任何访问成本~


我要回帖

更多关于 java单例模式 的文章

 

随机推荐