查看“设计模式:单例模式”的源代码
←
设计模式:单例模式
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:设计模式]] == 单例模式 == 单例(Singleton)模式的定义:指'''一个类只有一个实例,且该类能自行创建这个实例'''的一种模式。 单例模式有 3 个特点: # 单例类只有一个实例对象;【涉及多种实现方式】 # 该单例对象必须由单例类自行创建;【将构造函数标记为“private”】 # 单例类对外提供一个访问该单例的全局访问点。【提供“getInstance()”方法】 === 优点和缺点 === 优点: * 单例模式可以保证内存里只有一个实例,减少了内存的开销。 * 可以避免对资源的多重占用。 * 单例模式设置全局访问点,可以优化和共享资源的访问。 缺点: * 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。 * 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。 * 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。 === 应用场景 === 典型如:Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。 在计算机系统中,还有:Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。 对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。 * 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。 * 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。 * 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。 * 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。 * 频繁访问数据库或文件的对象。 * 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。 * 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。 == 结构 == 单例模式结构如下: : [[File:设计模式:单例模式.png|600px]] 单例模式的主要角色如下: # 单例类:包含一个实例且能自行创建这个实例的类。 #: <syntaxhighlight lang="java" highlight=""> // 饿汉式 public class SingleObject { //创建 SingleObject 的一个对象 private static SingleObject instance = new SingleObject(); //让构造函数为 private,这样该类就不会被实例化 private SingleObject(){} //获取唯一可用的对象 public static SingleObject getInstance(){ return instance; } public void showMessage(){ System.out.println("Hello World!"); } } </syntaxhighlight> # 访问类:使用单例的类。 #: <syntaxhighlight lang="java" highlight=""> public class SingletonPatternDemo { public static void main(String[] args) { //不合法的构造函数 //编译时错误:构造函数 SingleObject() 是不可见的 //SingleObject object = new SingleObject(); //获取唯一可用的对象 SingleObject object = SingleObject.getInstance(); //显示消息 object.showMessage(); } } </syntaxhighlight> == 实现(饿汉、懒汉、双重校验、静态内部类、枚举) == 单例模式的有多种实现方式: # '''懒汉式''':【懒(延迟)加载】 ## (线程不安全):因为没有加锁 synchronized,不支持多线程上单例,所以严格意义上它并不算单例模式。 ##: <syntaxhighlight lang="java" highlight=""> public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } </syntaxhighlight> ## (线程安全): synchronized 对方法加锁(粗粒度锁),保证单例,但效率较低。 ##: <syntaxhighlight lang="java" highlight="2,5"> public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } </syntaxhighlight> # '''饿汉式''': #: 它基于 '''classloader''' 机制避免了多线程的同步问题,不过,'''instance 在类装载时就实例化''',虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。 #* 【线程安全(classloader),非延迟加载】没有加锁,执行效率会提高。类加载时就初始化,浪费内存。这种方式比较常用,但容易产生垃圾对象。 #: <syntaxhighlight lang="java" highlight="2"> public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } </syntaxhighlight> # '''双重锁校验''':(DCL,即“double-checked locking”) #: 这种方式采用'''双锁机制''',安全且在多线程情况下能保持高性能。 #* 【线程安全(双锁),延迟加载】 #: <syntaxhighlight lang="java" highlight="2,5,7"> 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; } } </syntaxhighlight> ## '''“volatile”作用?''' ##: 【volatile一般用于多线程的可见性,但是这里是用来'''防止指令重排序的'''】 ##: <pre>保证指令有序:避免“singleton”内存分配过程中,由于“指令重排序”可能导致的错误;</pre> ##: JVM创建新的对象时主要要经过三步:分配内存,初始化构造器,将对象指向分配的内存的地址。而“指令重排序”可能导致后两个指令执行顺序相反: ##:: 先将分配好的内存地址指给instance,然后再进行初始化构造器,这时候后面的线程去请求“getInstance”方法时,会认为instance对象已经实例化了,直接返回一个引用。 ##:: 如果这时还没进行构造器初始化并且这个线程使用了instance的话,则会出现线程会指向一个未初始化构造器的对象现象,从而发生错误。 ## '''外层“if (singleton == null)”作用?''' ##: <pre>与线程安全、单例无关:仅避免每次进来都要加锁或者等待锁;</pre> ## '''“synchronized (Singleton.class) ”作用?''' ##: <pre>保证线程安全:避免多个线程同时执行内层代码块;</pre> ## '''内层“if (singleton == null)”作用?''' ##: <pre>保证单例:其用于避免多个线程先后进入同步块,生成多个实例</pre> # '''登记式/静态内部类''':【将 instance 放在静态内部类中,“getInstance()”返回静态内部类的域】 #: 这种方式能达到'''双检锁方式一样的功效''',但实现更简单。 #: <syntaxhighlight lang="java" highlight=""> 这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程。【一个类的静态属性只会在第一次加载类时初始化】 它跟第“饿汉”方式不同的是: 1、“饿汉”方式只要 Singleton 类被装载了,那么 instance 就会被实例化;(没有达到 lazy loading 效果) 2、“静态内部类”方式即使 Singleton 类被装载了,instance 也不一定被初始化(因为 SingletonHolder 类没有被主动使用)。 只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。 </syntaxhighlight> #* 对'''静态域'''使用延迟初始化,应使用此方式;而双检锁方式可在'''实例域'''需要延迟初始化时使用。 #* 【线程安全(classloader),延迟加载(调用“getInstance”方法时显式装载“SingletonHolder”类),仅适用于静态域】 #: <syntaxhighlight lang="java" highlight="2-4,8"> public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } </syntaxhighlight> # '''枚举''':【太完美?】 #: 这种实现方式还没有被广泛采用,但这是实现单例模式的'''最佳方法'''。它更简洁,'''自动支持序列化机制,绝对防止多次实例化'''。 #: 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 #* 不能通过 reflection attack 来调用私有构造方法。 #* 【线程安全(美剧),非延迟加载,'''自动支持序列化机制''','''防反射'''】 #: <syntaxhighlight lang="java" highlight="1"> public enum Singleton { INSTANCE; /* public void whateverMethod() { } */ } </syntaxhighlight> #* '''枚举的这种写法是无法通过“反射”来生成新的实例,因为枚举没有“public”构造方法'''。(而以上几种方法都可以通过反射破坏单例) 一般情况下,不建议使用“懒汉式”,建议使用“'''饿汉式'''”。 * 只有在要明确实现 lazy loading 效果时,才会使用“'''登记式'''”(静态内部类)。 * 如果涉及到反序列化创建对象时,可以尝试使用“枚举式”。 * 如果有其他特殊的需求,可以考虑使用“双检锁式”。 == 单例模式的扩展:有限的多例模式 == 单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArrayList 中,client 需要时可随机获取。 其结构图如图 5 所示: : [[File:设计模式:有限的多例模式(单例模式的扩展).png|600px]]
返回至“
设计模式:单例模式
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息