单例模式的定义:Ensure a class has only one instance, and provide a global point of access to it.确保一个类只有一个实例,并提供一个全局的访问点。
单例是比较简单的一种设计模式,简单到只有一个类图:
根据定义,我们设计一个类:
public class Singleton {
//仅此一个对象
private static final Singleton instance = new Singleton();
//构造方法私有化,确保只有这个类自身能实例化对象
private Singleton(){
}
//通过此public方法获取对象
public static Singleton getInstance() {
return instance;
}
}
它完全符合定义:私有化构造方法可以确保只有类自己实例化对象,从而保证了只有一个实例;public static的getInstance方法提供了一个全局的访问点。
对于单例的写法有两种,有的人称之为饿汉式和懒汉式,其实就是正常实例化和延迟实例化。懒汉和饿汉太难听了,我们不这么叫了。上面的例子就是正常实例化,即在JVM加载类的时候创建一个唯一的实例。但是想想这样做是有一些缺点的,如果这个实例在我们的应用中可能用不到岂不是白白浪费了?如果实例化过程中比较耗费资源, 负担过重,就不应该发生在JVM加载类的时候。解决方案就是延迟实例化,下面的程序是经典的延迟实例化单例模式实现:
public class SingletonLazyInitialization {
//唯一实例
private static SingletonLazyInitialization instance = null;
//构造方法私有化
private SingletonLazyInitialization(){}
//通过此public方法获取对象
public static SingletonLazyInitialization getInstance() {
if (null == instance) {
//初次调用此方法时,产生一个实例
instance = new SingletonLazyInitialization();
}
return instance;
}
}
这个写法在并发量较小时候,问题不是很大。但是并发量大时可能会出现bug:如果线程A执行到了instance = new SingletonLazyInstance();线程B执行到了if (null == instance),由于线程A还没有完成实例化,那么线程B也会再次执行instance = new SingletonLazyInstance();此时出现了两个实例,因此单例模式失败了...
处理多线程的方案
方案一,在getInstance方法同步
public static synchronized SingletonLazyInitialization getInstance()
这样解决多线程导致多实例是没问题的,方法上加synchronized会迫使每个线程进入方法之前等待前面的线程从方法中出来。但是性能问题随之而来,同步一个方法可能导致此方法的性能下降100倍!除非你的程序不考虑这些问题,否则没理由使用此解决方案。
方案二,并使用双重检查加锁
public static SingletonLazyInitialization getInstance() {
if (null == instance) {
//初次调用此方法时,产生一个实例
synchronized (SingletonLazyInitialization.class) {
if (null == instance) {
instance = new SingletonLazyInitialization();
}
}
}
return instance;
}
只有instance为空时候才进行加锁,很好的解决了性能问题。而且在synchronized块儿里面又进行了一次判断,貌似不会出现多实例的情况。但是...JVM在实例化一个对象时,大概有分配内存、初始化对象、将引用指向这个对象三个步骤要走,整个过程不是原子操作的,而且JVM不保证这三个步骤的顺序,因此有种情况就可能发生:线程A进入synchronized块儿执行instance = new SingletonLazyInstance(),JVM分配了内存,将引用指向了这个对象,但是对象并没有初始化完成;而此时线程B执行getInstance方法,发现instance已经不为null了,也就return一个为完成的对象,使用这样的对象会有问题啊。。。
对于这个问题,从JDK5开始我们有了解决方案,用volatile修饰instance对象:
private static volatile SingletonLazyInitialization instance = null;
volatile能保证happens-before关系,对instance对象的写操作都发生在读操作之前,保证多线程能正确的处理instance。
volatile水比较深,深入介绍请参考http://www.ibm.com/developerworks/cn/java/j-jtp06197.html。
双重检查加锁单例最好的写法是
public class SingletonLazyInitialization {
//用volatile修饰保证不会出现半初始化对象
private static volatile SingletonLazyInitialization instance = null;
private SingletonLazyInitialization(){}
//通过此public方法获取对象
public static SingletonLazyInitialization getInstance() {
if (null == instance) {
//初次调用此方法时进入synchronized块
synchronized (SingletonLazyInitialization.class) {
if (null == instance) {
instance = new SingletonLazyInitialization();
}
}
}
return instance;
}
}
volatile也有缺点:有一定的性能损耗,虽然相对于synchronized相比小得多。volatile不适用与jdk1.5之前的版本,导致双重检查加锁失败。
双重检查模式写起来比较复杂,有另外一种写法也开始实现延迟实例化,却不用double check麻烦:
/**
* 延迟初始化holder class模式
*/
public class SingletonLazyInitializationInner {
private SingletonLazyInitializationInner(){}
//定义一个静态内部类作为holder class
private static class SingletonLazyInitializationHolder {
private static final SingletonLazyInitializationInner instance = new SingletonLazyInitializationInner();
}
//全局访问点
public static SingletonLazyInitializationInner getInstance() {
return SingletonLazyInitializationHolder.instance;
}
}
这种方法叫lazy initialization holder class模式,根据JVM的机制规定,只有getInstance方法第一次被调用时内部类才被加载,从而实现了延迟实例化。加载过程是线程安全的:JVM保证类在初始化时,同步对变量的访问,一旦类初始化完成,JVM对后续的访问不会设置同步。
这种模式最大的优点是getInstance方法没有没有被同步,instance也没有被volatile修饰,所以没有增加任何成本。
单例模式的优点:
- 内存中只有一个实例,减少了内存开支,当可能存在很多实例时,使用单例节约内存。
- 只生成一个对象,减少了实例化过程的开支,尤其是当一个对象实例化过程消耗很多的资源,例如Hibernate的SessionFactory对象,要读取配置文件、设置数据库连接池等等
单例模式的缺点:
- 很明显一的,单例违反了单一职责原则。一个类做了自己本不应该做的事情,实例化自己。
- 单例模式无法被继承,因为构造方法是私有的
单例模式的应用场景:
- 对于不存在线程安全的类,尽量用单例模式,例如我们用spring管理bean,很多都是单例的。
- 创建对象消耗资源过多:用hibernate时,如果每次都创建一个SessionFactory对象,要疯了,必须是单例。
- 需要一个全局共享的数据,比如一个访问计数器
当然更多的还要结合项目的实际情况。
总结:
- 正常实例化和延迟实例化的抉择:正常初始化优先于延迟实例化,除非很有必要(实例化开销大而且不能接受类初始化性能低、或者此实例可能根本用不到),否则不要选择延迟实例化。延迟实例化虽然提高类初始化性能,但是降低了访问被延迟实例化变量的性能。
- 如何选择延迟实例化的写法:对于单例模式,延迟实例化选择lazy initialization holder class模式,它最优秀。但它不适用于实例变量延迟初始化,只能选择双重检查加锁+volatile修饰模式。
一个谣言:在instance没有被引用时候会被垃圾收集器清理掉
其实这只是jdk1.2版本之前的一个bug:即在只有单例类引用单例对象时,单例对象会被垃圾收集器回收掉。
分享到:
相关推荐
Java设计模式之单例模式的七种写法 单例模式是一种常见的设计模式,它确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机的驱动程序对象常...
python 设计模式之单例模式
设计模式之单例模式详解 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。 单例模式的实现主要是...
通过研磨设计模式之单例模式的资料,你可以深入理解单例模式的原理、实现方式及其优缺点,进一步提升自己的编程技能和设计思维。学习并熟练掌握设计模式,对于成为一名优秀的Java开发者至关重要。
Java设计模式之单例模式详解 一、单例模式概览 单例模式(Singleton Pattern)是面向对象设计模式中的一种,属于创建型模式。它确保一个类仅有一个实例,并提供一个全局访问点来访问该实例。单例模式的核心在于控制...
单例模式是软件设计模式中的一种经典模式,它保证了类只有一个实例存在,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。结合工厂模式,...
### PHP设计模式之单例模式详解 #### 一、引言 在软件工程领域,设计模式是一种被广泛接受的解决方案,用于解决特定类型的问题。PHP作为一种流行的服务器端脚本语言,同样可以从这些设计模式中受益。本文将详细介绍...
JAVA设计模式之单例模式(完整版)1[定义].pdf
单例模式是一种常用的设计模式,它的核心思想是在整个应用程序中,一个类只能有一个实例存在。单例模式常用于控制资源的共享,例如数据库连接池、日志服务等。单例模式有多种实现方式,常见的包括懒汉式、饿汉式以及...
细心整合和单例模式和工厂模式的几种模型,懒汉式,饿汉式,如何并发操作模式,等都有详细讲解
设计模式之单例模式,单列模式的几种实现形式,以及其优缺点,还有就是示例,对初步了解单列模式的有所帮助
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。 比如在某个服务器程序中,该服务器的配置信息存放...
本次我们将深入探讨两种设计模式——单例模式和装饰模式,它们在Java编程中都有着广泛的应用。 首先,让我们来理解“单例模式”。单例模式是一种创建型设计模式,其核心思想是保证一个类只有一个实例,并提供一个...
目录 单例模式的概念 单例模式的要点 单例模式类图 单例模式归类 单例模式的应用场景 单例模式解决的问题 单例模式的实现方式 单例模式实现方式对比 单例模式的概念 单例模式,顾名思义就是只有一个实例,并且由它...