单例模式--GOF的23个之一
前言:部分来自《Head First 设计模式》,不涉及任何商务往来,仅为学习使用,作为参照笔记。单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式(Singleton Pattern)
类图:
选择单例模式就是为了避免不一致状态。
优势:
1、 单例模式的类图是所有模式的类图中最简单的。
2、 单例模式让我们在任何时刻都只有一个对象。
3、 单例模式可以延迟实例化。
一、 用来创建独一无二的,只能有一个实例的对象的入场券。
单例模式
**可程序员约定==麻烦不言而喻。
**Java的静态变量控制==必须一开始就创建,创建非常耗资源且可能不用而浪费。
二、 如何保证一个对象只能被实例化一次?
这其实和实现有关。有些JVM的实现是:在用到的时候才创建对象。
附:如果我们不需要这个实例,他就永远不会产生。这就是“延迟加载”(Lazy Instantiaze)。
三、 单例模式的作用:常常被用来管理共享的资源。
四、单例模式的特征:
1、没有公开的构造器。
2、想要取得实例,必须以“请求”得到一个实例的方式,而不是自行实例化得到一个实例。类有一个静态方法,叫做getInstance()。调用这个方法,就立刻得到对象,随时可以工作。
附:事实上,对象可能是在这次调用的时候被创建出来的,也可能是以前早就被创建出来的。
五、单例模式中的两个原理
1、把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。要想取得单例实例,通过单例类是唯一途径。
2、提供对这个实例的全局访问点:当需要实例时,向类查询,它会返回单个实例。前面的例子利用延迟实例化的方式创建单件,这种做法对资源敏感的对象特别重要。
六、多线程与单例模式
1、困难:有多个线程都要执行这段代码。
解决:只要把getInstance()变成同步(synchronized)方法,多线程灾难几乎就可以轻易解决了。(后面“饿汉式单例模式“)
2、新困难:同步会使性能降低。
解决:这个问题更严重,只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不在需要同步这个方法了。之后u每次调用这个方法,同步都是一种累赘。(后面“双重检查加锁单例模式”)
七、静态带来:类的单件与对象的单件问题。
除非有绝对的必要使用类的单件,否则还是建议使用对象的单件。
八、如果程序有多个加载器又同时使用了单件模式,就会产生多个单件并存的怪异现象。
解决:自行指定类加载器,并指定同一个类加载器。
九、“一个类,一个责任”原则在单例模式被打破。
这里,单件类不只负责管理自己的实例(并提供全局访问),还在应用程序中担当角色,所以也可以被视为两个责任。但是单例模式在整个程序设计中本身就用得不多,破坏这个原则也是可以原谅的。
十、想要子类能工作顺利,基类必须实现注册表(Registry)功能。
1、构造函数私有不能继承。
2、构造函数保护或公共,不能保持单例模式。
(后面“登记式单例模式”)
十一、为何全局变量比单例模式差(其实前面已经谈到一部分)。
这个模式的目的:确保只有一个实例并提供全局访问。全局变量可以提供全局访问,但是不能确保是有一个实例。全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间(namespace)的污染。单例模式不鼓励这样的现象,但单例模式仍然可能被滥用。
十二、要点补充回顾
1、 在java中实现单例模式需要私有的构造器、一个静态方法和一个静态变量。
2、 如果不是采用第五版的Java2,双重检查加锁实现会失效。
3、 如果使用JVM1.2或之前版本,唏嘘建立单例注册表,以免垃圾收集器将单例回收。
下面是经典模式和四种使用模式
经典模式代码:
/**
* 传统模式(经典模式)。
* 不是线程安全的。
*/
public class Singleton {
private static Singleton uniqueInstance;
// 这里写其他有用的变量。
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 这里写其他有用的方法。
}
方法一 懒汉式单例模式
简介:在第一次调用的时候实例化。如果getInstance()的性能对应用程序不是很关键,就可以用这种方式。
/**
* 方法一 懒汉式单例模式。
* 基本解决线程问题,但是性能降低。
*/
publicclass Singleton {
private static Singleton uniqueInstance;
// 这里发其他有用的变量。
private Singleton() {}
public static synchronized SingletongetInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 这里放其他有用的方法。
}
方法二 饿汉式单例模式。
简介:在类初始化时,已经自行实例化。使用“急切”创建实例,而不用延迟实例化的做法。如果应用程序总是创建并使用单例模式,或者在创建和运行时方面的负担不太繁重,就可能想要急切(eagerly)创建单例。
/**
* 方法二 饿汉式单例模式。
*
*/
publicclass Singleton {
private static Singleton uniqueInstance =new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
方法三 双重检查加锁单例模式。
简介:懒汉式+提高性能。用“双重检查加锁”,在getInstance()中减少使用同步。利用双重检查加锁(double-ckecked locking),首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样一来,只有第一次同步,这已是我们想要的。
附:volatile关键词确保,当uniqueInstance变量被充实化成Singleton实例时,多个线程正确处理uniqInstance变量。
注意:如果性能是关心的重点,那么这个做法可以大大地减少getInstance()的时间耗费。同时,双重检查加锁不低于用1.4级更早版本的Java。
/**
* 方法三 双重检查加锁单例模式。
* 懒汉式+提高性能。
*
* 危险:这种模式采用第五版的Java2及以前会失效。
*/
publicclass Singleton {
private volatile static SingletonuniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = newSingleton();
}
}
}
return uniqueInstance;
}
}
方法四 登记式单例模式
简介:常用来给用子类的单例类。也叫注册表式。
注意:JVM1.2即以前版本,本方案不起作用。
importjava.util.HashMap;
importjava.util.Map;
/**
* 方法四 登记式单例模式。
* 警告:使用JVM1.2及之前的版本,本方案不起作用。
*/
publicclass Singleton {
private static Map<String, Singleton> map= new HashMap<String, Singleton>();
static{
Singleton singleton = new Singleton();
map.put(singleton.getClass().getName(),singleton);
}
protected Singleton(){}
public static Singleton getInstance(Stringname){
if(name==null){
name = Singleton.class.getName();
}
if(map.get(name)==null){
try{
map.put(name,(Singleton)Class.forName(name).newInstance());
}catch(InstantiationException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}catch(ClassNotFoundExceptione){
e.printStackTrace();
}
}
return map.get(name);
}
}
如有好的建议,可留言或发至笔者邮箱:fzb_xxzy@163.com