单例模式(饿汉式和懒汉式)

来源:互联网 发布:7405单片机管脚图 编辑:程序博客网 时间:2024/06/10 01:08

  以前学习单例的时候,只理解了简单部分。这次看DRP,对单例的饿汉式和懒汉式有了一些认识和对比。

   在实际的开发中,有些地方需要一个类只有一个实例。比如:网站在线人数的计数器,再比如IDE中的工具箱之类的等等。当需要这个类只有一个实例时,我们就需要使用到单例模式。单例模式有两种实现方式:懒汉式(延迟加载)和 饿汉式(预加载)。

   目前遇到的情况使用饿汉式的比较多,也因为它比较简单。代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ClientManager {     
  2.       
  3.     private static ClientManager instance = new ClientManager();   //静态私有成员,已初始化  
  4.       
  5.     public ClientManager(){ }  
  6.       
  7.     public static ClientManager getInstance(){     //静态,不用同步(类加载时已初始化,不会有多线程的问题)  
  8.         return instance;  
  9.     }  
  10. }  
   饿汉式:在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这里getInstance()是static的,不用同步(类加载时已初始化,不会有多线程的问题)


   饿汉式程序运行过程中会节省时间,但是实例不管有没有用到都会占用空间。在这方面懒汉式似乎比饿汉式优化。我们先看看代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ClientManager  
  2.  {  
  3.     private static ClientManager instance = null;  
  4.       
  5.     private ClientManager(){  
  6.           
  7.     }  
  8.   
  9.     public static  ClientManager getInstance(){     //静态,同步,公开访问点  
  10.         if(instance == null){  
  11.             return new ClientManager();  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  
    比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢。这里getInstance()是static的。我们来想象一下:要使用ClientManager,直接调用类的getInstance()方法。第一次的时候发现instance是null,然后就新建一个对象,返回出去;第二次再使用的时候,因为这个instance是static的,所以已经不是null了,因此不会再创建对象,直接将其返回。那么为什么有同步的问题?

    线程A希望使用ClientManager,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用ClientManager,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个ClientManager的对象——单例失败!

    对于这种情况,我们很容易就想到加锁来解决。那么加锁后是怎么样的?

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ClientManager{   
  2.   
  3.   private static ClientManager instance = null;   
  4.       
  5.   public synchronized static ClientManager getInstance() {     //给方法加锁  
  6.     if(instance == null) {   
  7.       instance = new ClientManager();   
  8.     }   
  9.     return instance;   
  10.   }   
  11.       
  12.   private ClientManager() {   
  13.        
  14.   }   
  15.       
  16.   
  17. }  
   getInstance()加上同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。但是还有出现这样一个性能问题:每次调用getInstants时都需要加锁,会降低运行速度。所以我们还可以进一步改进。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ClientManager {   
  2.   
  3.   private  static ClientManager instance = null;   
  4.   
  5.   public static ClientManager getInstance() {   
  6.     if (instance == null) {   
  7.       synchronized (ClientManager.class) {   
  8.         if(instance == null) {   
  9.           instance = new ClientManager();   
  10.         }   
  11.       }   
  12.     }   
  13.     return instance;   
  14.   }   
  15.   
  16.   private ClientManager() {   
  17.   
  18.   
  19.   }   
   这样的方法可以双重锁定。我们一般用让线程每次都加锁,而只是实例未被创建的时候再加锁处理,同时也能保证多线程的安全。为什么要进行两次instance==null的判断,这个交给大家自己想一下吧。就按照上面线程A和线程B这样的方式。

   最后,总结一下饿汉式和懒汉式的区别:饿汉式类加载时已初始化,不会有多线程的问题,使用简单。懒汉式是在需要时才对类进行实例化,但是有多线程问题,需要该考虑怎么加锁的问题。

0 0