单例模式深入分析

一. 单例模式简介

单例(Singleton)模式是使用最广泛的设计模式。其思想意图是保证一个类只有一个实例,并且提供类对象的全程访问。单实例对象应用的范围很广:如GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。使用全程对象能够保证方便地访问实例,但是不能保证只声明一个对象-也就是说除了一个全程实例外,仍然能创建相同类的本地实例。单实例模式通过类本身来管理其唯一实例,这种特性提供了问题的解决办法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全程访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。

二. 单例模式实现

单例模式在java里有两个实现方式:1、懒汉模式; 2、饿汉模式。

代码1、懒汉模式

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *Singleton的懒汉模式
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassSingleton
  8. {
  9. privatestaticSingletonsingleton;
  10. privateSingleton()
  11. {
  12. }
  13. publicstaticSingletongetInstance()
  14. {
  15. if(null==singleton)
  16. {
  17. singleton=newSingleton();
  18. }
  19. returnsingleton;
  20. }
  21. }
package cn.edu.hit.Singleton;/** * Singleton的懒汉模式 * @author yuanliming * @created 2009-9-2 */public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if(null == singleton) { singleton = new Singleton(); } return singleton; }}

代码2、饿汉模式

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *Singleton的饿汉模式
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassAnotherSingleton
  8. {
  9. privatestaticAnotherSingletonsingleton=newAnotherSingleton();
  10. privateAnotherSingleton()
  11. {
  12. }
  13. publicstaticAnotherSingletongetInstance()
  14. {
  15. returnsingleton;
  16. }
  17. }
package cn.edu.hit.Singleton;/** * Singleton的饿汉模式 * @author yuanliming * @created 2009-9-2 */public class AnotherSingleton{ private static AnotherSingleton singleton = new AnotherSingleton(); private AnotherSingleton() { } public static AnotherSingleton getInstance() { return singleton; }}

代码3. 测试代码

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *TestSingleton
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassSingletonTest
  8. {
  9. publicstaticvoidmain(String[]args)
  10. {
  11. Singletons1=Singleton.getInstance();
  12. Singletons2=Singleton.getInstance();
  13. System.out.println(s1==s2);//returntrue
  14. AnotherSingletons3=AnotherSingleton.getInstance();
  15. AnotherSingletons4=AnotherSingleton.getInstance();
  16. System.out.println(s3==s4);//returntrue
  17. }
  18. }
package cn.edu.hit.Singleton;/** * Test Singleton * @author yuanliming * @created 2009-9-2 */public class SingletonTest{ public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); //return true AnotherSingleton s3 = AnotherSingleton.getInstance(); AnotherSingleton s4 = AnotherSingleton.getInstance(); System.out.println(s3 == s4); //return true }}

两种实现模式的比较:
1、相同点:两种方式的构造函数都是私有的,对外的接口都是工厂方法。

2、不同点:饿汉式是在类装载的时候直接得到该类的实例,可以说是前期绑定的;懒汉式是后期绑定的,类加载的时候uniSingleton是空的,在需要的时候才被创建且仅创建一次。饿汉式的速度快,效率高,但是耗费系统资源;懒汉式则相反。

注意:懒汉式还存在一个问题,就是后期绑定不能确保对象只能被实例化一次。这就涉及到线程安全。

三. 单例模式的线程安全性探讨

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

单例模式下最关心的就是这个线程安全的问题了。上面我们提到懒汉模式会有线程安全的问题。当引入多线程时,就必须通过同步来保护getInstance() 方法。如果不保护 getInstance() 方法,则可能返回 Singleton 对象的两个不同的实例。假设两个线程并发调用 getInstance() 方法并且按以下顺序执行调用:

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:

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *Singleton的懒汉模式
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassSingleton
  8. {
  9. privatestaticSingletonsingleton;
  10. privateSingleton()
  11. {
  12. }
  13. publicstaticsynchronizedSingletongetInstance()
  14. {
  15. if(null==singleton)
  16. {
  17. singleton=newSingleton();
  18. }
  19. returnsingleton;
  20. }
  21. }
package cn.edu.hit.Singleton;/** * Singleton的懒汉模式 * @author yuanliming * @created 2009-9-2 */public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if(null == singleton) { singleton = new Singleton(); } return singleton; }}

此代码针对多线程访问 getInstance() 方法运行得很好。然而,分析这段代码,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 //2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定 instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是 synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。

因为代码4中只有//2需要同步,我们可以只将其包装到一个同步块中。得到的代码如下:

代码5:

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *Singleton的懒汉模式
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassSingleton
  8. {
  9. privatestaticSingletonsingleton;
  10. privateSingleton()
  11. {
  12. }
  13. publicstaticSingletongetInstance()
  14. {
  15. if(null==singleton)
  16. {
  17. synchronized(Singleton.class)
  18. {
  19. singleton=newSingleton();
  20. }
  21. }
  22. returnsingleton;
  23. }
  24. }
package cn.edu.hit.Singleton;/** * Singleton的懒汉模式 * @author yuanliming * @created 2009-9-2 */public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if(null == singleton) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; }}

可是代码5出现了代码1同样的问题。当 instance 为 null 时,两个线程可以并发地进入 if 语句内部。然后,一个线程进入 synchronized 块来初始化 instance,而另一个线程则被阻断。当第一个线程退出 synchronized 块时,等待着的线程进入并创建另一个 Singleton 对象。注意:当第二个线程进入 synchronized 块时,它并没有检查 instance 是否非 null。

为了解决代码5出现的问题,我们对instance进行两次检查,即“双重检查锁定”。代码如下:

代码6:

  1. packagecn.edu.hit.Singleton;
  2. /**
  3. *Singleton的懒汉模式
  4. *@authoryuanliming
  5. *@created2009-9-2
  6. */
  7. publicclassSingleton
  8. {
  9. privatestaticSingletonsingleton;
  10. privateSingleton()
  11. {
  12. }
  13. publicstaticSingletongetInstance()
  14. {
  15. if(null==singleton)
  16. {
  17. synchronized(Singleton.class)
  18. {
  19. if(null==singleton)
  20. {
  21. singleton=newSingleton();
  22. }
  23. }
  24. }
  25. returnsingleton;
  26. }
  27. }
package cn.edu.hit.Singleton;/** * Singleton的懒汉模式 * @author yuanliming * @created 2009-9-2 */public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if(null == singleton) { synchronized (Singleton.class) { if(null == singleton) { singleton = new Singleton(); } } } return singleton; }}

双重检查锁定在理论上能够保证代码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(尽管效率低下,但可以保证线程同步)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值