单例模式

原创 2016年08月30日 12:50:16

定义

单例模式顾名思义,在全局范围内提供唯一的实例对象供全局访问,一般用于全局的资源监控或者特定数量的资源访问控制(引申为特定数量的单例对象而非唯一,此处以单个实例对象为例),常用的示例如系统日志、单个打印机等对象,避免混乱发生错误,所以维持全局只有一个对象。

结构如下


单例模式作为对象创建性模式,所关心的只有两个点:

1、只有一个实例对象被创建

2、实例的创建时间可控制

结构

根据实例对象的创建时间,可以分为两种结构:

饿汉单例

在类加载时即创建实例对象,无须考虑后续并发环境对实例创建可能造成的影响(因为已经创建完成了,说这句废话是为了与懒汉单例对比),当然在实际使用单例对象时仍然需要加以同步控制,这是无论是饿汉或者懒汉都无法避免的。

参考代码

public class t{
	public static void main(String[] args){
		Singleton s1=Singleton.getInstance();
		Singleton s2=Singleton.getInstance();
		System.out.println(s1==s2);
	}
}
class Singleton{
	private static Singleton instance=new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return instance;
	}
}
参考上述代码可知,实例对象s1与s2表示同一个对象。使用该形式单例的一个缺陷是,不能控制对象创建的时间,即在类加载执行实例的创建工作,而无论当前是否需要使用实例对象,可能会存在资源浪费。如下

public class t{
	public static void main(String[] args){
		Singleton.test();
	}
}
class Singleton{
	private static Singleton instance=null;
	static{
		instance=new Singleton();
		System.out.println("instance initialized");
	}
	private Singleton(){}
	public static Singleton getInstance(){
		return instance;
	}
	public static void test(){
		System.out.println("just for test");
	}
}
输出

E:\>java t
instance initialized
just for test

懒汉单例

懒汉单例相对饿汉单例则是在需要使用单例对象时才进行实例创建工作,因为“懒”,所以能够实现所谓的延迟加载。但是也是因为“懒”,所以需要提供并发环境下的“安全”对象创建的保障。

这里所谓的“安全”,有两个方面需要考虑:

情形1:创建的实例对象多于一个

class Singleton{
	private static Singleton instance=null;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance==null){
			synchronized(Singleton.class){
				instance=new Singleton();
			}
		}
		return instance;
	}
}
在高并发环境下完成初次单例对象创建工作时,可能会存在两个或多个线程同时判断instance==null,该情形下会顺序等待执行同步代码块,由于在进入synchronized同步块之后,没有进行实例是否存在的判断,所以结果可能会创建多个实例对象。

情形2:使用的是未完成创建的实例对象(残缺品)
情形1可能会创建多个单例对象,原因是在进入同步代码块后没有进行单例对象存在与否的判断,改进代码如下
class Singleton{
	private static Singleton instance=null;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance==null){
			synchronized(Singleton.class){
				if(instance==null){
					instance=new Singleton();				
				}
			}
		}
		return instance;
	}
}
就是在进入同步块后再次进行判断,该情形下当多个线程判断instance==null,则顺序等待进入同步代码块,当第二个线程进入同步块,判断instance!=null,则可以直接使用第一个线程已经“创建”完成的单例对象,甚至于所有新来的线程当判断instance!=null时,不需等待同步代码块,直接使用已经“创建”完成的单例对象。
真实的情况可能跟表面呈现出的不一致,这里造成不一致的原因就是指令重排序,指令重排序本身是一种编译器或处理器的优化策略,在保证结果正确的前提下对中间的指令进行优化处理,最简单情形如下:
a=0;b=0;
Action set{
  a=1;
  b=2;
}
Action get{
  if(b==2){
    return a;
  }
  return -1;
}
线程1执行set,线程2执行get,因为a、b的赋值之间没有依赖关系,所以可以进行指令重排序,因此线程2中,当b值为2时,a的值不确定是否已经赋值为1(关于依赖关系,具体见JVM内存模型有序性)。
关于单例对象的创建简写为以下三个步骤:
1、分配对象空间
2、对象初始化
3、将对象地址赋值给instance引用变量
此处可能存在的问题就是,在步骤2尚未完成,步骤3已经完成,即表面上已经完成实例对象的“创建”,即instance!=null。
所以此处需要改进为
class Singleton{
	private static volatile Singleton instance=null;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance==null){
			synchronized(Singleton.class){
				if(instance==null){
					instance=new Singleton();				
				}
			}
		}
		return instance;
	}
}
只是在instance引用变量上加上volatile关键字,volatile提供的有两个功能:
1、内存可见性
2、作为内存屏障,即禁止指令重排序
(volatile介绍太普遍,此处只强调一点,并不能保证原子性,可以保证可见性、有序性)
此处依赖的就是其内存屏障的作用(该方式也就是传说中的双重检查锁定DCL)。
不过观察该方式,仍存在的问题是,synchronized关键字保证同步性自然是不可少的,但是volatile作为一种轻量级同步(它不能保证同步,前两句已说其不能保证原子性),限制了一些优化措施。所以有一种利用静态内部类的实现形式如下
class Singleton{
	
	private Singleton(){}
	private static class Holder{
		final static Singleton instance=new Singleton();
	}
	public static Singleton getInstance(){
		return Holder.instance;
	}
}
静态内部类在调用时才进行加载,并且加载机制可以保证线程安全性,所以此处将synchronized和volatile提供的同步访问和有序性等安全保障,都转移到静态内部类的加载机制中完成。

总结

单例模式作为向全局提供单一实例访问的模式,有两种实例对象构造方式:饿汉单例和懒汉单例,关键就在与对象的创建时间的差异,因为懒汉单例并不是在类加载是完成实例创建,所以需要提供安全性保障,避免实例对象不单一或者不完整的情况。



版权声明:本文为博主原创文章,未经博主允许不得转载。

Selenium使用单例模式

在项目中使用Selenium进行自动化,非常自然想到用单例对WebDriver进行管理,出于如下考虑:         1)提高成功率。多个实例会使得出现多个浏览器窗口,界面识别会出现问题,影响成功...
  • wanglian2017
  • wanglian2017
  • 2017年06月13日 21:58
  • 359

一个单例模式的简单例子

ex1: public class Singleton    {        private static final Singleton singleton = null;           p...
  • silence1214
  • silence1214
  • 2009年03月29日 14:56
  • 23500

单例模式的自动析构

总所周知,单例模式如果不主动调用,在进程结束时是不会析构的,而是仅仅把内存释放掉而已 所以,如果希望在析构时进行某些操作,在进程结束前,可以主动调用析构函数,如下面类中的delMe函数(xSingl...
  • mengmingqiang
  • mengmingqiang
  • 2011年08月31日 15:43
  • 2739

Java中用单例模式有什么好处

Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(...
  • huanbia
  • huanbia
  • 2017年05月12日 10:05
  • 2354

单例模式的三种常用的形式

自己做下笔记: //单例模式:他的应用场景—如果多个程序要操作一个对象中的数据,那么就要保证只有一个对象。 //饿汉式 publicclassSingle {     privatesta...
  • jiyidehao
  • jiyidehao
  • 2016年02月28日 12:26
  • 486

C#单例模式的几种实现方式

C#单例模式的几种实现方式
  • yanlovehan
  • yanlovehan
  • 2016年11月09日 10:07
  • 2128

单例模式 -通俗易懂的讲解

什么是单例模式: 保证一个类有且仅有一个实例,并提供它的全局访问点。 单例模式有什么用: 其实单例的作用在于避免产生太多实例,也就是不用new那么多次,new一次就占用一次内存的资源,new多了...
  • yangxujia
  • yangxujia
  • 2013年04月10日 01:41
  • 916

单例模式中静态变量初始化与不初始化有什么区别

public class Singleton { private static Singleton obj = new Singleton(); public static int count...
  • woshixiongjin
  • woshixiongjin
  • 2014年05月23日 17:16
  • 618

java单例模式原理

Singleton 是一种创建性模型,它用来确保只产生一个实例,并提供一个访问它的全局访问点.对一些类来说,保证只有一个实例是很重要的,比如有的时候,数据库连接或 Socket 连接要受到一定的限制,...
  • xiaoxiaocoder
  • xiaoxiaocoder
  • 2012年10月17日 14:08
  • 2716

单例模式高并发问题

单例模式下,并发量很高,获得对象有两种方式:一种是使用懒汉模式,即系统初始化时初始化对象;第二种是细化锁的粒度,使用读写锁。 第二种方法如下: 单例虽然没有缓存写的那么平凡,如果在getinstanc...
  • gongzi2311
  • gongzi2311
  • 2015年02月10日 17:26
  • 2122
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:单例模式
举报原因:
原因补充:

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