码迷,mamicode.com
首页 > 其他好文 > 详细

设计模式之单例模式

时间:2015-01-10 08:58:31      阅读:218      评论:0      收藏:0      [点我收藏+]

标签:设计模式   单例模式   懒汉模式   饿汉模式   

        单例模式是我们开发过程中很常用也是比较简单的一种设计模式,使用单例模式的目的是使类的一个对象成为系统的唯一一个实

例。举个大家都熟知的例子----Windows任务管理器,在Windows的任务栏里右击选择"任务管理器",会打开一个"任务管理器"窗口

(如下图所示),再连续同样操作几次看看到底打开的是多个窗口还是一个窗口,在不修改Windows内核的情况下,打开的肯定是唯一

个窗口,也就是说"任务管理器"在我们Windows操作系统中是一个单例,那么微软为什么要这么设计呢?我们可以从以下两点来分

析:第一,如果打开的俩"任务管理器"窗口内容是一致的,那么就是重复对象,"任务管理器"获取电脑的实时状态信息是需要耗费

一定的系统资源的,重复对象显然是在浪费资源。第二,如果打开的窗口显示的内容不一致,那么情况可就糟糕了,一个窗口显示

CPU占用率5%,另一个显示CPU占用率90%,那么到底哪个是对的呢?综上可知,微软把"任务管理器"设计成单例模式是明智的。

       技术分享



          回到软件开发中,有时候我们需要节约系统资源,有时为了确保系统中只有唯一一个实例,所有的操作只能基于这个实例。为了确保这个实例的唯一性,我们需要借助单例模式来实现。

        以任务管理器为例,任务管理器里有大量成员方法,我选取显示进程和服务为例,对单例模式进行简要介绍。

        实现单例模式有三个要点:第一,某个类只能有一个实例;第二,它必须能自行创建一个实例;第三,它必须能自行向整个系统提供一个全局访问方法。

        一.饿汉模式:

package com.moluo.test;
   /**
    * @author xingzhemoluo
    */
public class TaskManager {
	//饿汉模式   线程安全
	//instance在类装载时就实例化,避免了多线程同步问题
	private static TaskManager instance = new TaskManager();
	private TaskManager() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
	public static TaskManager getInstance() {
		return instance;
	}
}
       首先声明并创建一个静态的实例,然后将构造行数声明为private,因为这样可以防止在其他类中采取直接New的方式来直接创建一个实例。最后提供一个全局访问方法,讲这个instance提供给外界访问。这个instance在类加载的时候就实现了实例化,它是线程安全的,但没有实现延迟加载,因为实例是由JVM自行创建的,但我们有时候不需要这个实例,但却被JVM加载了,显然是浪费系统资源,所以我们必须改进这种"饿汉模式"来实现延迟加载,于是就有了下面那种"懒汉模式"。

       二.懒汉模式:

package com.moluo.test;
/**
 * @author xingzhemoluo
 */
public class TaskManager1 {
	//懒汉模式   线程不安全
	private static TaskManager1 instance;
	private TaskManager1() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
	public static TaskManager1 getInstance() {
		if (instance == null) {
			instance = new TaskManager1();
		}
		return instance;
	}
}
        与第一种"饿汉模式" 的区别就是,声明instance的时候并没有直接创建,只是声明罢了,然后在全局访问方法里,也就是getInstance()里执行instance = new TaskManager1()来创建一个实例。当系统调用这个getInstance()方法的时候才会执行实例化,显然这个"懒汉模式"实现了延迟加载,即使用才加载,不使用不加载。但这个"懒汉模式"显然不是线程安全的,因为当多个线程同时访问这个getInstance方法,并且都是在同一时刻通过了If语句,那么就会创建多个实例。因此此方法在多线程并发访问的时候可能会造成实例不是唯一的,所以我们必须加同步锁来使线程安全。

        三.改进过的懒汉模式:

package com.moluo.test;
/**
 * @author xingzhemoluo
 */
public class TaskManager2 {
	//使用同步锁改进过的懒汉模式以处理多个线程同时访问的问题。  
	private static TaskManager2 instance;
	private TaskManager2() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
	public static synchronized TaskManager2 getInstance() {
		if (instance == null) {
			instance = new TaskManager2();
		}
		return instance;
	}
}
       与上面那种"懒汉模式"相比,这个改进过的"懒汉模式"只是在全局访问方法getInstance()的签名加上同步锁:synchronized,此改进方法是由JVM来对线程进行锁定,所以线程是安全的,因此创建的实例也是唯一的。但此方法有个不好的缺点:每次调用getInstance()方法的时候都要进行线程锁定判断,因此性能较低,所以该方法还是需要改进。

       四.再次改进过的懒汉模式:

package com.moluo.test;
/**
 * @author xingzhemoluo
 */
public class TaskManager3 {
	//解决了线程安全问题,也实现了延迟加载,但有可能产生多个实例
	private static TaskManager3 instance;
	private TaskManager3() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
    //将synchronized放到里面来实现延迟加载
	public static  TaskManager3 getInstance() {
		if (instance == null) {
			synchronized (TaskManager3.class) {
				instance = new TaskManager3();
			}
		}
		return instance;
	}
}
        与上述改进过的"懒汉模式"相比,在全局访问方法创建实例的时候加同步锁,此方法实现了延迟加载,线程也是安全的,但却还是可能产生多个实例。

        假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象。

       五.双重判断

package com.moluo.test;
/**
 * @author xingzhemoluo
 */
public class TaskManager4 {
	//双重锁定,但使用了volatile修饰词,屏蔽了jvm的一些代码优化,代码效率不高
	private static volatile TaskManager4 instance;
	private TaskManager4() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
    //将synchronized放到里面,并且进行双重判断来实现延迟加载
	public static  TaskManager4 getInstance() {
		if (instance == null) {
			synchronized (TaskManager4.class) { //锁定代码块
				if (instance == null) {
					instance = new TaskManager4();
				}
			}
		}
		return instance;
	}
}
       与上述改进方法相比,此改进方法知识在锁定代码块里再加上一个判断是否为空的条件,注意:双重判断必须在声明Instance的时候加上volatile来修饰,这样才可以正确处理多线程,才能实现单例。但是vollatile会屏蔽Java虚拟机做的一些代码优化,效率较低。所以,还需要改进。

       六.静态内部类实现单例

package com.moluo.test;
/**
 * @author xingzhemoluo
 */
public class TaskManager5 {
    //在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用
	private TaskManager5() {
		// 执行初始化操作,必须是private,因为要防止其他类采用new的方式来创建实例
	}
	public void displayService() {
		// 显示服务
	}
	public void displayProcess() {
		// 显示进程
	}
	public static  TaskManager5 getInstance() {
		return ClassHolder.instance;
	}
	//内部类
	private static class ClassHolder {
		private static TaskManager5 instance = new TaskManager5();
	}
}
        此方法首先是创建了一个静态内部类ClassHolder,并在这个静态内部类里声明并创建一个instance,然后在全局访问方法里调用这个instance,以此来实现延迟加载,并实现单例。因为静态内部类只有在调用getInstance()的时候才会主动去加载静态内部类,这样就实现了延迟加载,并且由JVM来确保这个Instance只被初始化一次,同时没有线程锁定,所以性能较好,显然这种方法是最好的一种单例模式实现方法,既实现了单例,又实现了延迟加载,同时效率又高。

单例模式总结
       单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。
       1.主要优点
       单例模式的主要优点如下:
       (1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
       (2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系      统的性能。
       (3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系      统资源,又解决了单例单例对象共享过多有损性能的问题。
2.主要缺点
单例模式的主要缺点如下:
(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了      产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
        (3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利              用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
      3.适用场景
       在以下情况下可以考虑使用单例模式:
      (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创              建一个对象。
      (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。


参考资料:http://blog.csdn.net/lujiancs/article/details/8278843


尊重版权,转载请注明本文链接

                                      

                                           欢迎关注行者摩罗微信公众号(xingzhemoluo),共同交流编程经验,扫描下方二维码即可;  



技术分享





设计模式之单例模式

标签:设计模式   单例模式   懒汉模式   饿汉模式   

原文地址:http://blog.csdn.net/xingzhemoluo/article/details/42561025

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!