java 单例模式有几种,各有什么不同(面试常问)?

java 单例模式有几种,各有什么不同(面试常问)?

1 个解决方案

AllenJiang
中间件研发,关注微信公众号 : 小哈学Java, 回复"666", 即可免费领取10G学习&面试资料

大体来说,java 中的单例模式的常用写法有七种

第一种:懒汉,线程不安全

public class Singleton {  

  private static Singleton instance;  
  
  private Singleton (){}   
  
  public static Singleton getInstance() {  
  if (instance == null) {  
	  instance = new Singleton();}  
  return instance;  
  }  
} 

这种写法是懒加载的写法,比较致命的是,在多线程情况下,会出现多次实例化 Singleton 对象。

第二种:懒汉,线程安全的

public class Singleton {
	private static Singleton instance;
	private Singleton (){}

	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();}
		return instance;
	}
}

这种写法能够很好的运行在多线程的环境中,也具备懒汉加载的优势,但是,很遗憾的是,效率很低,99% 的情况下不需要同步。

第三种:饿汉式

public class Singleton {  

	 private static Singleton instance = new Singleton();  
	 
	 private Singleton (){}
	 
	 public static Singleton getInstance() {  
	 return instance;  
}

这种方式是基于 java 类加载机制避免了多线程同步的问题,不过正因为如此,实例在装载时就实例化,而且导致实例化的原因还有很多种,有时候,我们是不愿意将其实例化的。而且单例模式大多数实例化都是调用 getInstance() 方法,违背了懒加载的设计。

第四种: 饿汉,变种

public class Singleton {  
	  private Singleton instance = null;  
	  static {  
	  instance = new Singleton();}  
	  private Singleton (){}
	  public static Singleton getInstance() {  
	  return this.instance;  
	  }  
} 

这里利用了 static 块来实例化对象,实际上和第三种差不多。

第五种: 静态内部类

public class Singleton {  
	  private static class SingletonHolder {  
	  private static final Singleton INSTANCE = new Singleton();}  
	  private Singleton (){}
	  public static final Singleton getInstance() {  
		  return SingletonHolder.INSTANCE;  
	  }  
} 

这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,我想让他延迟加载,另外一方面,我不希望在 Singleton 类加载时就实例化,因为我不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第六种:枚举

 public enum Singleton {  
	 INSTANCE;  
	 public void whateverMethod(){}  
 } 

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

第七种: 双重校验锁

public class Singleton {  
	  private volatile static Singleton singleton;  
	  private Singleton (){}   
	  public static Singleton getSingleton() {  
	  if (singleton == null) {  
		  synchronized (Singleton.class) {  
		  if (singleton == null) {  
			  singleton = new Singleton();}  
		 }  
	 }  
	 return singleton;  
	 }  
 } 

这个是第二种方式的升级版,俗称双重检查锁定,在 JDK1.5 之后,双重检查锁定才能够正常达到单例效果。


这里有两个问题需要注意:

1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些 servlet 容器对每个 servlet 使用完全不同的类 装载器,这样的话如果有两个 servlet 访问一个单例类,它们就都会有各自的实例。

2、如果 Singleton 实现了 java.io.Serializable 接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对第一个问题修复的办法是:

private static Class getClass(String classname)      
									   throws ClassNotFoundException {     
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     

	if(classLoader == null)     
	   classLoader = Singleton.class.getClassLoader();     

	return (classLoader.loadClass(classname));     
 }     
} 

对第二个问题修复的办法是:

public class Singleton implements java.io.Serializable {     

	public static Singleton INSTANCE = new Singleton();     
	protected Singleton(){}     
	private Object readResolve() {     
		  return INSTANCE;     
	}    
}   

总结:

不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。

  • 懒汉
  • 恶汉
  • 双重校验锁
  • 枚举
  • 静态内部类