[把你的理性思维慢慢变成条件反射]
本文,我们讲介绍单例模式,文章主题结构与上文一致。惯例,先来看看我们示例工程的环境:
操作系统:win7 x64
其他软件:eclipse mars,jdk8
-------------------------------------------------------------------------------------------------------------------------------------
经典问题:
日志Logger对象。
思路分析:
要点一:在任意时刻,系统中只能有数量小于等于一个的该对象。
要点二:无论是自己创建,还是被其他对象创建或引用,均不能破坏唯一性。
【由于单例模式是较为常用的一种设计模式。所以我们调整一下顺序,先说明概念,然后在展示示例代码】
模式总结:
单例模式结构图:
单例模式:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
组成部分:
Singleton(单例类):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
模板代码:
懒汉式单例:
特点:
- 延迟加载,只有到真正使用是才生成一个单例对象供调用对象使用。并且,以后不会再生成新的该对象。
- 拥有一个private修饰的静态变量
- 拥有一个private修饰的构造方法,方式被外部的对象调用。
- 拥有一个public修饰的静态工厂方法,返回一个具体的实例。
package com.csdn.ingo.gof_Singleton;
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
package com.csdn.ingo.gof_Singleton;
public class WIndow {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1==s2){
System.out.println("s1==s2");
}
}
}
Singleton通过将构造方法限定为private避免了本类在外部进行实例化操作,在同一个虚拟机的范围内,Singleton的唯一示例只能通过getInstance()方法进行获取。(事实上,通过Java的反射机制,能够访问private类型的方法,此处对此问题暂不做讨论)
懒汉式单例的问题:
当发生多线程同时使用该对象时,会发生线程不安全的问题。很有可能生成多个对象。
解法:
增加线程锁,双重锁检查,静态内部类(推荐)等方法。
①增加同步锁:
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
②双重锁检查:
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
③静态内部类:(内部类在调用时才进行加载)
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
饿汉式单例:
特点:
- 在类进行加载时就创建了一个单例对象,并且之后一直使用该对象。
- 拥有一个private final static 修饰的,并且自身带初始化的变量。
package com.csdn.ingo.gof_Singleton.two;
public class Singleton {
private Singleton() {
}
private final static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
以上两种单例模式区别:
线程安全性:
饿汉式的单例模式:一定是线程安全的。懒汉式的单例模式:其自身是非线程安全的,但是能够通过其他①②③三种方式实现安全性。
性能:
饿汉式的单例模式:
在启动实例化过程时,就会创建一个单例对象。因此,在时间上而言,实例化启动的时间较长,使用时的调用时间相对较短。另外,该单例对 象会一直存在于内存当中。
懒汉式的单例模式:
在启动实例化过程中,没有创建一个单例对象,而是在使用时才创建一个单例对象。因此,在时间上而言,实例化启动的时间较短,使用时的 调用时间相对教程。另外,在实例化之后,该单例对象也会一直存在于内存当中。
除此之外:
上文第①种方式:每次都需要使用同步锁,对性能会产生不利影响。
上文第②种方式:在第一次调用时,使用同步锁。后续不再使用同步锁。对性能影响较为有限。
上文第③种方式:内部类在初始化时未生成一个单例对象,而是等到第一次调用时才生成。在调用时,由于Classloader的特殊性,保证实例化的对象有且只有一个单例对象。因此,其表现效果类似于②。
枚举式单例:
特点:
- 通过枚举定义的方式声明与使用单例对象
- 线程安全:枚举的创建过程本身是线程安全的
- 不会因为序列化而产生新实例:在传统的单例模式中,一旦实现了Serializable接口,其就不再是单例的对象了,因为readObject()方法内次总是返回一个新的示例对象,此过程类似于Java中的构造器。而对于枚举实现单例,其实从JVM中防止了此问题。
package com.csdn.ingo.gof_Singleton.thire;
public enum Singleton {
Instance; // 定义一个枚举的元素,就代表Singleton的一个实例
private Singleton() {
}
}
双重锁Volatile检查的单例模式:
特点:
- volatile关键字:保证该对象始终是最新的值(在Java1.5版本之后上文的双重锁存在漏洞,具体原因,请各位看官查看volatile解释即可)
- 其他类似于上文双重锁检查。
package com.csdn.ingo.gof_Singleton.five;
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
反思:
应用场景:
- 在系统内只需要有且只有一个该对象时,如日志对象,ID生成器对象,等等。
- 客户端对单例对象只允许使用一个公共访问点。
优点:
- 单例模式提供了对实例对象的唯一入口。严格控制了客户的方式与路径。
- 节约系统资源,节约该对象的响应时间。
- 单例模式可以扩展为指定数量的对象生成器。其效果类似于连接池的创建过程,取得资源与效率的平衡。
缺点:
- 单例模式不符合“开闭原则”。对单例的维护只能修改单例类本身。
- 单例模式不符合“单一职责原则”。单例类本身负责创建对象,有包含了对象的其他方法。
- Java垃圾回收机制会将长时间未使用的,实例化的共享的单例对象进行回收,导致该对象的状态丢失。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被说了很多遍的设计模式---单例模式 结束
参考资料:
图书:《大话设计模式》
其他博文:
http://blog.csdn.NET/lovelion/article/details/7563445
http://cantellow.iteye.com/blog/838473
https://my.oschina.net/zhoujy/blog/134958
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
http://www.cnblogs.com/zhengbin/p/5654805.html
http://javarevisited.blogspot.jp/2012/07/why-enum-singleton-are-better-in-java.html