定义:保证一个类,只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
单例模式是一种对象创建型模式,使用单例模式可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例 对象。单例模式的要有三个:1)某个类只有一个实例;2)它必须自行创建这个实例;3)它必须自行向整个系统提供这个实例 2.单例模式深入分析
单例模式适合于一个类只有一个实例的情况,如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个 软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。 注意:使用单例模式时,构造函数一定是private类型的。 在计算机系统中,需要管理的资源包括软件外部资源,例如,每台计算机可以有若干个打印机,但是有一个Printer Spooler,以避免两个打印作业同时 传输到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。需要管理的软件 内部资源包括负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等,这些部件都必须集中管理。 1) 单例模式的第一个版本,采用的是"饿汉式",也就是当类加载进来时就立即实例化当前类的对象,这种方式比较消耗计算机的资源,代码如下:
2)单例模式提供的第二个版本,"懒汉式",在单线程中能够非常好的工作,但是在多线程中存在线程安全问题。
3)为解决多线程问题,采用对函数进行同步的方式,但是比较浪费资源,因为每次都要进行同步检查,而实际中真正需要好只是在第一次实例化时检查
4)既可以解决懒汉式,又可以解决多线程问题,还可以资源浪费的现象,看上去是一种不同的选择
另外也可以看一篇文章:
一. 单例模式简介 单例(Singleton)模式是使用最广泛的设计模式。其思想意图是保证一个类只有一个实例,并且提供类对象的全程访问。单实例对象应用的范围很广:如GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。使用全程对象能够保证方便地访问实例,但是不能保证只声明一个对象-也就是说除了一个全程实例外,仍然能创建相同类的本地实例。单实例模式通过类本身来管理其唯一实例,这种特性提供了问题的解决办法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全程访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。 二. 单例模式实现 单例模式在java里有两个实现方式:1、懒汉模式; 2、饿汉模式。 代码1、懒汉模式
代码2、饿汉模式
代码3. 测试代码
两种实现模式的比较: 2、不同点:饿汉式是在类装载的时候直接得到该类的实例,可以说是前期绑定的;懒汉式是后期绑定的,类加载的时候uniSingleton是空的,在需要的时候才被创建且仅创建一次。饿汉式的速度快,效率高,但是耗费系统资源;懒汉式则相反。 注意:懒汉式还存在一个问题,就是后期绑定不能确保对象只能被实例化一次。这就涉及到线程安全。 三. 单例模式的线程安全性探讨 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
单例模式下最关心的就是这个线程安全的问题了。上面我们提到懒汉模式会有线程安全的问题。当引入多线程时,就必须通过同步来保护 1. 线程 1 调用 getInstance() 方法并决定 instance 在 //1 处为 null。 2. 线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。 3. 线程 2 调用 getInstance() 方法并在 //1 处决定 instance 为 null。 4. 线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。 5. 线程 2 在 //3 处返回 Singleton 对象引用。 6. 线程 2 被线程 1 预占。 7. 线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。 8. 线程 1 在 //3 处返回这个对象。 这样,getInstance()方法就创建了2个Singleton对象,与单例模式的意图相违背。通过使用synchronized同步getInstance() 方法从而在同一时间只允许一个线程执行代码。代码如下: 代码4:
此代码针对多线程访问 getInstance() 方法运行得很好。然而,分析这段代码,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 //2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定 instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是 synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。 因为代码4中只有//2需要同步,我们可以只将其包装到一个同步块中。得到的代码如下:
可是代码5出现了代码1同样的问题。当 instance 为 null 时,两个线程可以并发地进入 if 语句内部。然后,一个线程进入 synchronized 块来初始化 instance,而另一个线程则被阻断。当第一个线程退出 synchronized 块时,等待着的线程进入并创建另一个 Singleton 对象。注意:当第二个线程进入 synchronized 块时,它并没有检查 instance 是否非 null。 为了解决代码5出现的问题,我们对instance进行两次检查,即“双重检查锁定”。代码如下: 代码6:
双重检查锁定在理论上能够保证代码6只创建一个Singleton对象。假设有下列事件序列: 1. 线程 1 进入 getInstance() 方法。 2. 由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。 3. 线程 1 被线程 2 预占。 4. 线程 2 进入 getInstance() 方法。 5. 由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。 6. 线程 2 被线程 1 预占。 7. 线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance。 8. 线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。 9. 线程 1 被线程 2 预占。 10. 线程 2 获取 //1 处的锁并检查 instance 是否为 null。 11. 由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。 看起来,双重检查锁定既解决了代码4的效率低下问题,又解决了代码5的线程安全性问题。但是它并不能保证它会在单处理器或多处理器计算机上顺利运行,根源在于 Java 平台内存模型。深入了解可以参考相关资料。 四.单例模式的选择 无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何 JVM 实现上都能顺利运行。 如果只在单线程环境下运行,最好使用代码1。 如果涉及到多线程环境,最好使用代码2,也可以使用代码4(尽管效率低下,但可以保证线程同步)。
|
java中的单例模式
第二部分 单例模式
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.