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

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

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

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

public class ClientManager {   
	
	private static ClientManager instance = new ClientManager();   //静态私有成员,已初始化
	
	public ClientManager(){	}
	
	public static ClientManager getInstance(){     //静态,不用同步(类加载时已初始化,不会有多线程的问题)
		return instance;
	}
}
   饿汉式:在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这里getInstance()是static的,不用同步(类加载时已初始化,不会有多线程的问题)


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

public class ClientManager
 {
	private static ClientManager instance = null;
	
	private ClientManager(){
		
	}

	public static  ClientManager getInstance(){     //静态,同步,公开访问点
		if(instance == null){
			return new ClientManager();
		}
		return instance;
	}
}
    比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢。这里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的对象——单例失败!

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

public class ClientManager{ 

  private static ClientManager instance = null; 
    
  public synchronized static ClientManager getInstance() {     //给方法加锁
    if(instance == null) { 
      instance = new ClientManager(); 
    } 
    return instance; 
  } 
    
  private ClientManager() { 
     
  } 
    

}
   getInstance()加上同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。但是还有出现这样一个性能问题:每次调用getInstants时都需要加锁,会降低运行速度。所以我们还可以进一步改进。

public class ClientManager { 

  private  static ClientManager instance = null; 

  public static ClientManager getInstance() { 
    if (instance == null) { 
      synchronized (ClientManager.class) { 
        if(instance == null) { 
          instance = new ClientManager(); 
        } 
      } 
    } 
    return instance; 
  } 

  private ClientManager() { 


  } 
   这样的方法可以双重锁定。我们一般用让线程每次都加锁,而只是实例未被创建的时候再加锁处理,同时也能保证多线程的安全。为什么要进行两次instance==null的判断,这个交给大家自己想一下吧。就按照上面线程A和线程B这样的方式。

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


 

你可能感兴趣的:(单例模式(饿汉式和懒汉式))