单例模式(Singleton Pattern)
什么是单例模式?
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式有以下特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
介绍
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:
一个全局使用的类频繁地创建与销毁。
何时使用:
当您想控制实例数目,节省系统资源的时候。
如何解决:
判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:
构造函数是私有的。
应用实例:
- Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理,不能两台打印机打印同一个文件。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 要求生产唯一序列号。
- WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:
getInstance()方法中需要使用同步锁synchronized(Singleton.class)防止多线程同时进入造成instance被多次实例化。
静态对象:
- 静态对象的数据在全局是唯一的,一改都改。如果你想要处理的东西是整个程序中唯一的,弄成静态是个好方法。 非静态的东西你修改以后只是修改了他自己的数据,但是不会影响其他同类对象的数据。
- 引用方便。直接用类名.静态方法名或者类名.静态变量名就可引用并且直接可以修改其属性值,不用get和set方法。
- 保持数据的唯一性。此数据全局都是唯一的,修改他的任何一处地方,在程序所有使用到的地方都将会体现到这些数据的修改。有效减少多余的浪费。
- static final用来修饰成员变量和成员方法,可简单理解为“全局常量”。对于变量,表示一旦给值就不可修改;对于方法,表示不可覆盖。
类图
常见的实现方式
饿汉式
优点:
- 没有加锁,执行效率会提高。
- 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
缺点:
- 类加载时就初始化,浪费内存,容易产生垃圾对象,jvm垃圾收集器是不会回收单例对象(静态)。——垃圾回收属个人观点。
package com.match.singleton; |
懒汉式
此处只介绍线程安全的懒汉式。线程不安全的懒汉式只是没加同步锁(synchronized),严格意义上它并不算单例模式。
优点:
- 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,能够在多线程中很好的工作。
缺点:
- 必须加锁 synchronized 才能保证单例,但加锁会影响效率。
package com.match.singleton; |
双检锁/双重校验锁(DCL,即 double-checked locking)
注:要求JDK1.5起。
优点:
- 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,采用双锁机制,安全且在多线程情况下能保持高性能。
缺点:
- 实现起来比较复杂。
package com.match.singleton; |
登记式/静态内部类——Initialization on Demand Holder (IoDH)技术
描述:
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了classloder机制来保证初始化instance时只有一个线程,它跟饿汉式不同的是:饿汉式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading 效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化instance。想象一下,如果实例化instance很消耗资源,所以想让它延迟加载,另外一方面,又不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。
package com.match.singleton; |
枚举
注:要求JDK1.5起。
优点:
- 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,不能通过reflection attack来调用私有构造方法,支持多线程。
缺点:
- 不具备延时加载(lazy loading)。
package com.match.singleton; |
如何防止反射和反序列化漏洞
package com.match.singleton; |