关于单例模式的最全总结

原创 2015年11月19日 01:05:48
单例类是最简单的一个OOP设计模式,然而单例模式并不那么简单,因此技术面试中面试官往往会问到它。
这里总结了关于java中单例模式的一系列问题。仅供总结和分享,请不要用来难为面试的娃们。

1. 1最简单的单例--饿汉单例

饿汉单例指的是在类加载时完成单例对象实例化。下面是一个简饿汉单例的示例:
package Singletons;

public class GreedySingleton{
	//private static
	private static GreedySingleton instance = new GreedySingleton();
	
	//private
	private GreedySingleton(){}
	
	//static
	public static GreedySingleton getInstance(){
		return instance;
	}
}


简单得没啥可说,只有一点需要注意:该单例的实现中getInstance()方法可以并发访问,不需要再添加synchronized之类的同步
其实关于饿汉单例还有一个学习中无法想到的问题,这个问题只有真正使用时才可能遇到,具体见下一节。

1.2 饿汉单例也不简单

话不多说,直接上码:
public class GreedySingleton_{
	//A:
	private static GreedySingleton_ instance = new GreedySingleton_();
	//B:
	private static int[] wallet;
	//C:
	static{
		wallet = new int [2];
		wallet[0] = 1;
	}
	//D:
	private GreedySingleton_(){
		if(wallet == null){
			wallet = new int[2];
		}
		wallet[1] = 2;
	}
	
	public static GreedySingleton_ getInstance(){
		return instance;
	}
	
	public void openUrWallet(){
		System.out.println("["+wallet[0]+","+wallet[1]+"]");
	}
	
	public static void main(String[] args){
		GreedySingleton_.getInstance().openUrWallet();
	}
}
输出结果:
[1,0]
你可能猜对了,也可能分析错了,这都不重要,重要的是代码真正的执行情况:
程序从main方法开始,
首先遇到类GreedySingleton_,于是加载该类:
类加载经过class加载、验证、解析、准备,然后进入初始化阶段,执行<clinit>方法:
clinit方法首先执行 private static GreedySingleton_ instance = new GreedySingleton_():
这里是一个new指令,于是先判断GreedySingleton_是否已经加载,由于类加载工作除了初始化过程外其他过程均已完成,判定该类已经加载
于是分配一段堆内存来存放GreedySingleton_实例对象,并执行<init>方法:
构造方法外没有非静态属性的初始化和赋值操作,所以这里<init>的任务就是执行构造方法GreedySingleton_():
这是wallet还处于null状态,因此执行wallet = new int[2]; wallet[1] = 2;
 从<init>退出,将<init>中分配内存的引用赋给instance属性
        继续执行<clinit>的后续代码:wallet = new int [2]; wallet[0] = 1; 这时wallet被指向另一个数组对象,其内容为[1,0] (之所以第二个未赋值的wallet[1]==0,原因是数组也是一个类,类实例化时new指令能保证分配到的是一段全零内存)
以上是代码执行过程。

从以上分析可以看到,<init>方法在<clinit>方法执行过程中被执行,除非非常了解JVM规范,否则无法知道代码真正的含义。
正常来讲,应该先执行<clinit>方法,等类加载过程全部完成,才可以执行<init>方法,否则只会带来麻烦而不会达到任何好处。
为了保证始终<clinit>先于<init>,可以遵循以下几个原则:
0. 尽量避免自身类型的类属性
1. 尽量避免在<clinit>涵盖代码部分中实例化自身引用
2. 尽量将自身类型实例化操作置于<clinit>涵盖代码的最后部分

2. 复杂点的单例--懒汉单例

懒汉单例就是等首次使用时才创建对象实例:
package Singletons;

public class LazySingleton {
	private static LazySingleton instance = null;
	
	private LazySingleton(){}
	
	//同步是为了防止instance属性未实例化时同时来了n个线程,他们同时到达代码:if(instance==null)
	//都看到instance未创建,于是每人创建了一个出来
	public synchronized LazySingleton getInstance(){
		if(instance==null)
			instance = new LazySingleton();
		return instance;
	}
}

但是有一个很大的问题:每次访问都要进行同步操作,大部分时间都花在同步上了。解决办法就是引入Double-Check。
《Java与模式》一书提到Java语言无法实现Double-Check(原因是由于指令重排导致实例化完成与属性的引用赋值先后无法预测,导致有些线程得到未初始化完成的单例对象)。但是早在JDK1.5对volatile进行修复之后,这个问题就已经解决:
package Singletons;

public class LazySingleton_ {
	//volatile
	private volatile static LazySingleton_ instance = null;
	
	private LazySingleton_(){}
	
	public static LazySingleton_ getInstance(){
		//Double-Check:
		if(instance == null){
			synchronized (LazySingleton_.class) {
				if(instance == null)
					instance = new LazySingleton_();
			}
		}
		
		return instance;
	}
}

volatile阻止了对instance的操作进程指令重排,同时保证了其可视性

3. 真正的单例--安全单例

前面的单例均通过构造方法私有化来保证"单"的要求。
所谓安全单例,指不能通过反射得到其第二个实例。
安全单例可以通过内嵌类、抽象类两种方式来实现。
其中,内嵌类的方式得到的同时也是懒汉单例,是一个延迟加载的安全的单例,所以最为推荐。
详见我的另一篇博客http://blog.csdn.net/pbooodq/article/details/49125355

单例模式的两种实现方式

1.   饿汉模式: #include using namespace std; class Singleton { public: static Singleton& getInst (void...
  • meetings
  • meetings
  • 2015年08月06日 16:27
  • 912

c++类中的静态成员函数总结

参考http://www.cnblogs.com/carbs/archive/2012/04/04/2431992.html 1.c++中可以对类中私有成员中的静态变量初始化吗? 一般情况下对类中私有...
  • u011499425
  • u011499425
  • 2016年09月30日 16:57
  • 213

单例模式(Singleton)- 最易懂的设计模式解析

前言今天我来全面总结一下Android开发中最常用的设计模式 - 单例模式。 关于设计模式的介绍,可以看下我之前写的:1分钟全面了解“设计模式” 目录1. 引入1.1 解决的是什么问题之前说过,设...
  • carson_ho
  • carson_ho
  • 2016年08月16日 17:15
  • 4088

Hql语句总结【不断更新】

1:基本查询语句 from是最简单的语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。 例如: from Person  表明从Person持久化类中选出全部的实例。注意这里的实体类P...
  • u013628152
  • u013628152
  • 2015年01月10日 13:13
  • 901

深度学习史上最全总结(文末有福利)

深度学习(Deep Learning),这是一个在近几年火遍各个领域的词汇,似乎所有的算法只要跟它扯上关系,瞬间就显得高大上起来。但其实,从2006年Hinton在Science上的论文算起,深度学习...
  • a1b2c3d4123456
  • a1b2c3d4123456
  • 2015年09月01日 22:27
  • 3473

909422229__JAVA最全总结【必备】

一、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。 ...
  • a909422229
  • a909422229
  • 2016年08月31日 18:48
  • 510

年终总结最全攻略-来自人民日报

年底快要到了,又要写年终总结了,人民日报教你如何写好年终总结。
  • dylloveyou
  • dylloveyou
  • 2017年11月18日 21:03
  • 712

最全单例模式

在所有的设计模式中,单例模式是我们在项目开发中最为常见的设计模式之一,而单例模式有很多种实现方式,你是否都了解呢?高并发下如何保证单例模式的线程安全性呢?如何保证序列化后的单例对象在反序列化后任然是单...
  • q262800095
  • q262800095
  • 2016年09月29日 11:28
  • 204

单例模式最全

在GoF的23种设计模式中,单例模式是比较简单的一种。然而,有时候越是简单的东西越容易出现问题。下面就单例设计模式详细的探讨一下。   所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例...
  • jkdcoach
  • jkdcoach
  • 2017年03月13日 17:57
  • 61

深度学习_总结篇

这里拼的不是工作量,拼的是算法,是对数据的理解.参加个比赛或者技术选型,人家用啥模型,咱也用啥模型,即使只调参,也需要理解数据和掌握数学工具.不明白原理,还是没法跟人家拼....
  • xieyan0811
  • xieyan0811
  • 2017年11月07日 12:32
  • 96
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:关于单例模式的最全总结
举报原因:
原因补充:

(最多只允许输入30个字)