1.Singleton的概念:
《设计模式》一书中对于Singleton模式是这样定义的:保证一个类有且仅有一个实例,并且提供了一个全局的访问点。
作为对象的创建模式[GOF95], 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
2.使用的目的:
2.使用的目的:
在很多操作中,比如建立目录,打印机,数据库连接都需要这样的单线程操作,如果一个类有多个实例存在的话,就可能带来并发的问题。
这就提出了一个问题:一般每个类中的构造函数都是public,如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这就是单例模式需要解决的问题。
为了防止单态模式的类被多次实例化,应将类的构造器设成private或protected,这样就保证了只能通过静态方法获得类实例。如果构造器设置为private,那么就不能单例的子类。而该静态方法则保证每次返回的实例都是同一个,这就需将该类的实例设置成类属性,由于该属性需要被静态方法访问,因此该属性应设成静态属性。
3.使用的环境:
这就提出了一个问题:一般每个类中的构造函数都是public,如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这就是单例模式需要解决的问题。
为了防止单态模式的类被多次实例化,应将类的构造器设成private或protected,这样就保证了只能通过静态方法获得类实例。如果构造器设置为private,那么就不能单例的子类。而该静态方法则保证每次返回的实例都是同一个,这就需将该类的实例设置成类属性,由于该属性需要被静态方法访问,因此该属性应设成静态属性。
3.使用的环境:
在什么情况下需要使用单例模式呢?
(1)、当类只能有一个实例而且使用者可以从一个众所周知的访问点访问它。
(2)、当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。这点就引出了登记式单例。
(1)、当类只能有一个实例而且使用者可以从一个众所周知的访问点访问它。
(2)、当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。这点就引出了登记式单例。
具体的分析情况:
第一、控制资源的使用,通过线程同步来控制资源的并发访问;
第二、控制实例产生的数量,达到节约资源的目的。
第三、作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
比如,数据库连接池的设计一般采用单例模式,数据库连接是一种数据库资源。软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的。当然,使用数据库连接池还有很多其它的好处,可以屏蔽不同数据数据库之间的差异,实现系统对数据库的低度耦合,也可以被多个系统同时使用,具有高可复用性,还能方便对数据库连接的管理等等。数据库连接池属于重量级资源,一个应用中只需要保留一份即可,既节省了资源又方便管理。所以数据库连接池采用单例模式进行设计会是一个非常好的选择。
在我们日常使用的在Windows中也有不少单例模式设计的组件,象常用的文件管理器。由于Windows操作系统是一个典型的多进程多线程系统,那么在创建或者删除某个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象。采用单例模式设计的文件管理器就可以完美的解决这个问题,所有的文件操作都必须通过唯一的实例进行,这样就不会产生混乱的现象。
再比如,每台计算机可以有若干个打印机,如果每一个进程或者线程都独立地使用打印机资源的话,那么我们打印出来的结果就有可能既包含这个打印任务的一部分,又包含另外一个打印任务的一部分。所以,大多数的操作系统最终为打印任务设计了一个单例模式的假脱机服务Printer Spooler,所有的打印任务都需要通过假脱机服务进行。
实际上,配置信息类、管理类、控制类、门面类、代理类通常被设计为单例类。像Java的Struts、Spring框架,.Net的Spring.Net框架,以及Php的Zend框架都大量使用了单例模式。
4.单例模式的要点:
单例模式的要点有三个:
一是某各类只能有一个实例;
二是它必须自行创建这个事例;
三是它必须自行向整个系统提供这个实例。
由于Java 语言的特点,使得单例模式在Java 语言的实现上有自己的特点。这些特点主要表现在单例类如何将自己实例化上。
5.Java实现
1、饿汉式 在下面这个例子中,这个类被加载时,静态变量m_instance 会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被创建出来了。
1、饿汉式 在下面这个例子中,这个类被加载时,静态变量m_instance 会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被创建出来了。
- //饿汉式:
- public class EagerSingleton {
- /** @label Creates */
- private static final EagerSingleton m_instance = new EagerSingleton();
- /**
- * 私有的默认构造函数
- */
- private EagerSingleton() { }
- /**
- * 静态工厂方法
- */
- public static EagerSingleton getInstance() {
- return m_instance;
- }
- }
//饿汉式:
public class EagerSingleton {
/** @label Creates */
private static final EagerSingleton m_instance = new EagerSingleton();
/**
* 私有的默认构造函数
*/
private EagerSingleton() { }
/**
* 静态工厂方法
*/
public static EagerSingleton getInstance() {
return m_instance;
}
}
2、懒汉式
- //懒汉式
- //使用到才实例化 不使用不实例化,比如你使用此类的其他静态方法 而饿汉式只要用到就被实例化
- public class LazySingleton
- {
- private LazySingleton() { }
- //对静态工厂方法使用了同步化,以处理多线程环境
- synchronized public static LazySingleton getInstance()
- {
- if (m_instance == null)
- {
- m_instance = new LazySingleton();
- }
- return m_instance;
- }
- }
//懒汉式
//使用到才实例化 不使用不实例化,比如你使用此类的其他静态方法 而饿汉式只要用到就被实例化
public class LazySingleton
{
private LazySingleton() { }
//对静态工厂方法使用了同步化,以处理多线程环境
synchronized public static LazySingleton getInstance()
{
if (m_instance == null)
{
m_instance = new LazySingleton();
}
return m_instance;
}
}
注:(1)、从资源利用效率角度来讲,饿汉式比懒汉式单例类稍差些。
(2)、从速度和反应时间角度来讲,饿汉式比懒汉式单例类稍好些。
懒汉式和恶汉式的详细对比:
懒汉模式,它的特点是运行时获得对象的速度比较慢,但加载类的时候比较快。它在整个应用的生命周期只有一部分时间在占用资源。
饿汉模式,它的特点是加载类的时候比较慢,但运行时获得对象的速度比较快。它从加载到应用结束会一直占用资源。
这两种模式对于初始化较快,占用资源少的轻量级对象来说,没有多大的性能差异,选择懒汉式还是饿汉式都没有问题。但是对于初始化慢,占用资源多的重量级对象来说,就会有比较明显的差别了。所以,对重量级对象应用饿汉模式,类加载时速度慢,但运行时速度快;懒汉模式则与之相反,类加载时速度快,但运行时第一次获得对象的速度慢。
从用户体验的角度来说,我们应该首选饿汉模式。我们愿意等待某个程序花较长的时间初始化,却不喜欢在程序运行时等待太久,给人一种反应迟钝的感觉,所以对于有重量级对象参与的单例模式,我们推荐使用饿汉模式。
而对于初始化较快的轻量级对象来说,选用哪种方法都可以。如果一个应用中使用了大量单例模式,我们就应该权衡两种方法了。轻量级对象的单例采用懒汉模式,减轻加载时的负担,缩短加载时间,提高加载效率;同时由于是轻量级对象,把这些对象的创建放在使用时进行,实际就是把创建单例对象所消耗的时间分摊到整个应用中去了,对于整个应用的运行效率没有太大影响。
3、登记式
- //登记式:
- import java.util.HashMap;
- public class RegSingleton
- {
- static private HashMap m_registry = new HashMap();
- static
- {
- RegSingleton x = new RegSingleton();
- m_registry.put( x.getClass().getName() , x);
- }
- /**
- * 保护的默认构造子
- */
- protected RegSingleton() {}
- /**
- * 静态工厂方法,返还此类惟一的实例
- */
- static public RegSingleton getInstance(String name)
- {
- if (name == null)
- {
- name = "com.javapatterns.singleton.demos.RegSingleton";
- }
- if (m_registry.get(name) == null)
- {
- try
- {
- m_registry.put( name,Class.forName(name).newInstance() ) ;
- }
- catch(Exception e)
- {
- System.out.println("Error happened.");
- }
- }
- return (RegSingleton) (m_registry.get(name) );
- }
- /**
- * 一个示意性的商业方法
- */
- public String about()
- {
- return "Hello, I am RegSingleton.";
- }
- }
//登记式:
import java.util.HashMap;
public class RegSingleton
{
static private HashMap m_registry = new HashMap();
static
{
RegSingleton x = new RegSingleton();
m_registry.put( x.getClass().getName() , x);
}
/**
* 保护的默认构造子
*/
protected RegSingleton() {}
/**
* 静态工厂方法,返还此类惟一的实例
*/
static public RegSingleton getInstance(String name)
{
if (name == null)
{
name = "com.javapatterns.singleton.demos.RegSingleton";
}
if (m_registry.get(name) == null)
{
try
{
m_registry.put( name,Class.forName(name).newInstance() ) ;
}
catch(Exception e)
{
System.out.println("Error happened.");
}
}
return (RegSingleton) (m_registry.get(name) );
}
/**
* 一个示意性的商业方法
*/
public String about()
{
return "Hello, I am RegSingleton.";
}
}
它的子类RegSingletonChild 需要父类的帮助才能实例化。
- import java.util.HashMap;
- public class RegSingletonChild extends RegSingleton
- {
- public RegSingletonChild() {}
- /**
- * 静态工厂方法
- */
- static public RegSingletonChild getInstance()
- {
- return (RegSingletonChild)
- RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" );
- }
- /**
- * 一个示意性的商业方法
- */
- public String about()
- {
- return "Hello, I am RegSingletonChild.";
- }
- }
import java.util.HashMap;
public class RegSingletonChild extends RegSingleton
{
public RegSingletonChild() {}
/**
* 静态工厂方法
*/
static public RegSingletonChild getInstance()
{
return (RegSingletonChild)
RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" );
}
/**
* 一个示意性的商业方法
*/
public String about()
{
return "Hello, I am RegSingletonChild.";
}
}
缺点:
1、RegSingletonChild类的构造函数是public,就等于允许了以这样方式产生实例而不在父类的登记中。
2、GoF 曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。
6.注意
1、getInstance() 是静态的;
2、构造函数是protected或private;
3、尽量不要将类声明为静态的;
7.单例模式的应用:
1.我们在浏览BBS、SNS网站的时候,常常会看到“当前在线人数”这样的一项内容。对于
这样的一项功能,我们通常的做法是把当前的在线人数存放到一个内存、文件或者数据库中,每次用户登录的时候,就会马上从内存、文件或者数据库中取出,在其基础上加1后,作为当前的在线人数进行显示,然后再把它保存回内存、文件或者数据库里,这样后续登录的用户看到的就是更新后的当前在线人数;同样的道理,当用户退出后,当前在线人数进行减1的工作。所以,对于这样的一个需求,我们按照面向对象的设计思想,可以把它抽象为“在线计数器”这样一个对象,具体实现如下:
Java
代码:
//
在线人数计数器
class OnlineCounter {
//
在线人数
private int onlineCount = 0;
//
构造函数
public OnlineCounter(){
//
从文件或者数据库读取数据
,
假如读出来的数据是
100
this.onlineCount = 100;
}
//
在用户登录后,在线人数加
1
public void incCount(){
this.onlineCount++;
}
//
在用户退出后,在线人数减
1
public void decCount(){
this.onlineCount--;
}
//
保存在线人数
public void saveCount(){
}
//
获取在线人数
public int getCount(){
return onlineCount;
}
//
测试函数
public static void main(String[] args) {
try{
OnlineCounter onlineCounter = new OnlineCounter();
System.out.println("
在线人数:
" +onlineCounter.getCount());
onlineCounter.incCount();
System.out.println("
在线人数:
" + onlineCounter.getCount());
onlineCounter.decCount();
System.out.println("
在线人数:
" + onlineCounter.getCount());
}catch(Exception err){
}
}
}
网站代码中凡是用到计数器的地方,只要new一个计数器对象,然后就可以获取、保存、增加或者减少在线人数的数量。不过,我们的代码实际的使用效果并不好。假如有多个用户同时登录,那么在这个时刻,通过计数器取到的在线人数是相同的,于是他们使用各自的计数器加1后存入文件或者数据库。这样操作后续登陆的用户得到的在线人数,与实际的在线人数并不一致。所以,把这个计数器设计为一个全局对象,所有人都共用同一份数据,就可以避免类似的问题,这就是我们所说的单例模式的其中的一种应用。
2.另外角度看单例:
单例模式是创建模式中普遍采用的一种。使用单例模式可以确保某个类只会有一个实例被创建。单例模式是通过下面的思想来保证的:不让类以外的任何事物创建对象的实例。通常来讲,单例可以缩减内存的需求。实现方式也有很多种。
如果你知道将要创建的实例是一个子类,那么将父类声明为抽象类并提供一个方法来获得当前的实例。在AWT包中,Toolkit类就是一个典型的代表。Toolkit的构造器是public的。
public Toolkit()
并且类具有一个方法getDefaultToolkit()用于获得特定的子类。在这里,子类是和平台相关的。
public static Toolkit getDefaultToolkit()
在Linux平台上,特定的子类是sun.awt.X11.XToolkit。然而,你并不需要知道这些细节,因为访问的时候我们是通过子类的抽象父类Toolkit实现的。
Collator类是单例模式中另一个例子,只是实现略有不同。它提供了两个getInstance()方法。无参数的版本得到默认locale的Collator。也可以通过传递一个参数来获得指定locale的Collator。通过同样的locale参数多次获得的Collator实例是相同的。Collator的构造器是protected类型的。在J2SE标准类库中,我们还可以找到很多这样的例子。
我们可能会认为限制一个类的构造器的访问会自动将这个类设计为单例模式的,但是并非如此。
Calendar就是这样一个例子,Calendar的构造器是protected的,提供了一个getInstance()方法来获得这个类的实例,每次调用getInstance()都会创建一个新的实例。因此它不是单例模式的。
当创建自己的单例类的时候,确保只有一个实例被创建:
public class MySingleton {
private static final MySingleton INSTANCE =
new MySingleton();
private MySingleton() {
}
public static final MySingleton getInstance() {
return INSTANCE;
}
}
静态方法getInstance()返回这个类的一个实例。注意即使这个实例需要是子类,
也无须修改API。
一般来说,不需要提供一个getInstance()方法,因为INSTANCE变量可以声明为public的。
但是,getInstance()方法可以提供更好的灵活性,尤其是以后系统的设计发生变化的时候。
出色的虚拟机实现可以将getInstance()方法进行内联。
编写一个好的单例模式的类并非这么简单,如果需要使得自己的单例类是可序列化的,那么必须提供一个readResolve()方法:
/**
* Ensure Singleton class
*/
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
提供readResolve()方法后,反序列化的时候将只有一个对象产生,无论调用了多少次getInstance()方法。如果不提供readResolve()方法,当反序列化的时候,每次都会创建一个新的对象实例。
如果只需要使用一个单独的资源,并且需要共享这个单独资源的状态信息的时候,单例模式是非常有用的。在设计的时候就标记好单例模式的需求可以简化开发。然而,有些时候我们意识不到需要使用单例模式直到系统出现了性能问题,这个时候我们就需要重构代码/例如,你可能发现系统的性能在下滑,原因是程序中重复的创建了同一个类的很多对象,通过应用单例模式则可以很好的避免创建通常的对象。这可以减少系统用来创建对象的时间,也可以节省垃圾收集器用来释放这些实例的时间。
总的来说,如果不想创建一个类的多个实例的时候就使用单例模式。如果构造器中不需要其他的操作,那么就提供一个空的私有构造器,如果需要子类的话就提供一个protected类型的。否则,默认情况下,系统会提供一个公共的构造器。
需要注意的是在一个给定的类装载器中单例模式保证是唯一的。如果在多个不同的企业容器中使用通常的类的时候,那么需要为每个容器提供一个实例。
单例模式通常和工厂模式一起使用,象单例模式一样。工厂模式也是创建模式。它描述了如何扩展一个特殊的对象,更典型的情况是实现一个特殊的接口,做实际的对象创建工作。工厂模式的典型例子是Swing中的BorderFactor类。这个类有一系列的静态方法可以返回不同的Border对象。它掩藏了子类的实现细节,允许工厂直接调用构造器来获得接口的实现。例如:
Border line = BorderFactory.createLineBorder(Color.RED);
JLabel label = new JLabel("Red Line");
label.setBorder(line);
事实上,BorderFactor创建了一个LineBorder,BorderFactor创建LineBorder的细节没有暴露给开发者。在特殊的例子中尼可以调用LineBorder构造器,但是在使用工厂模式的时候,你一般不能这么做。
很多时候,单例模式的类实现返回一个对象用作工厂来创建另一个类的实例。例如PopupFactory创建Popup对象的时候:
调用PopupFactory的getSharedInstance()方法可以获得单例工厂:
PopupFactory factory = PopupFactory.getSharedInstance();
然后调用工厂的getPopup()方法来创建Popup对象,需要指定父组件,内容和位置:
Popup popup = factory.getPopup(owner, contents, x, y);
工厂模式在安全上下文中使用很频繁。例如,下面将创建一个证书工厂,然后产生一个证书流。
FileInputStream fis = new FileInputStream(filename);
CertificateFactory cf =
CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(fis);
虽然,工厂模式不一定要和单例模式一起使用,但是通常这两个模式在一起使用的时候比较频繁。
如果你知道将要创建的实例是一个子类,那么将父类声明为抽象类并提供一个方法来获得当前的实例。在AWT包中,Toolkit类就是一个典型的代表。Toolkit的构造器是public的。
public Toolkit()
并且类具有一个方法getDefaultToolkit()用于获得特定的子类。在这里,子类是和平台相关的。
public static Toolkit getDefaultToolkit()
在Linux平台上,特定的子类是sun.awt.X11.XToolkit。然而,你并不需要知道这些细节,因为访问的时候我们是通过子类的抽象父类Toolkit实现的。
Collator类是单例模式中另一个例子,只是实现略有不同。它提供了两个getInstance()方法。无参数的版本得到默认locale的Collator。也可以通过传递一个参数来获得指定locale的Collator。通过同样的locale参数多次获得的Collator实例是相同的。Collator的构造器是protected类型的。在J2SE标准类库中,我们还可以找到很多这样的例子。
我们可能会认为限制一个类的构造器的访问会自动将这个类设计为单例模式的,但是并非如此。
Calendar就是这样一个例子,Calendar的构造器是protected的,提供了一个getInstance()方法来获得这个类的实例,每次调用getInstance()都会创建一个新的实例。因此它不是单例模式的。
当创建自己的单例类的时候,确保只有一个实例被创建:
public class MySingleton {
private static final MySingleton INSTANCE =
new MySingleton();
private MySingleton() {
}
public static final MySingleton getInstance() {
return INSTANCE;
}
}
静态方法getInstance()返回这个类的一个实例。注意即使这个实例需要是子类,
也无须修改API。
一般来说,不需要提供一个getInstance()方法,因为INSTANCE变量可以声明为public的。
但是,getInstance()方法可以提供更好的灵活性,尤其是以后系统的设计发生变化的时候。
出色的虚拟机实现可以将getInstance()方法进行内联。
编写一个好的单例模式的类并非这么简单,如果需要使得自己的单例类是可序列化的,那么必须提供一个readResolve()方法:
/**
* Ensure Singleton class
*/
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
提供readResolve()方法后,反序列化的时候将只有一个对象产生,无论调用了多少次getInstance()方法。如果不提供readResolve()方法,当反序列化的时候,每次都会创建一个新的对象实例。
如果只需要使用一个单独的资源,并且需要共享这个单独资源的状态信息的时候,单例模式是非常有用的。在设计的时候就标记好单例模式的需求可以简化开发。然而,有些时候我们意识不到需要使用单例模式直到系统出现了性能问题,这个时候我们就需要重构代码/例如,你可能发现系统的性能在下滑,原因是程序中重复的创建了同一个类的很多对象,通过应用单例模式则可以很好的避免创建通常的对象。这可以减少系统用来创建对象的时间,也可以节省垃圾收集器用来释放这些实例的时间。
总的来说,如果不想创建一个类的多个实例的时候就使用单例模式。如果构造器中不需要其他的操作,那么就提供一个空的私有构造器,如果需要子类的话就提供一个protected类型的。否则,默认情况下,系统会提供一个公共的构造器。
需要注意的是在一个给定的类装载器中单例模式保证是唯一的。如果在多个不同的企业容器中使用通常的类的时候,那么需要为每个容器提供一个实例。
单例模式通常和工厂模式一起使用,象单例模式一样。工厂模式也是创建模式。它描述了如何扩展一个特殊的对象,更典型的情况是实现一个特殊的接口,做实际的对象创建工作。工厂模式的典型例子是Swing中的BorderFactor类。这个类有一系列的静态方法可以返回不同的Border对象。它掩藏了子类的实现细节,允许工厂直接调用构造器来获得接口的实现。例如:
Border line = BorderFactory.createLineBorder(Color.RED);
JLabel label = new JLabel("Red Line");
label.setBorder(line);
事实上,BorderFactor创建了一个LineBorder,BorderFactor创建LineBorder的细节没有暴露给开发者。在特殊的例子中尼可以调用LineBorder构造器,但是在使用工厂模式的时候,你一般不能这么做。
很多时候,单例模式的类实现返回一个对象用作工厂来创建另一个类的实例。例如PopupFactory创建Popup对象的时候:
调用PopupFactory的getSharedInstance()方法可以获得单例工厂:
PopupFactory factory = PopupFactory.getSharedInstance();
然后调用工厂的getPopup()方法来创建Popup对象,需要指定父组件,内容和位置:
Popup popup = factory.getPopup(owner, contents, x, y);
工厂模式在安全上下文中使用很频繁。例如,下面将创建一个证书工厂,然后产生一个证书流。
FileInputStream fis = new FileInputStream(filename);
CertificateFactory cf =
CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(fis);
虽然,工厂模式不一定要和单例模式一起使用,但是通常这两个模式在一起使用的时候比较频繁。
8.小结:
1.Singleton模式可以方便的进行扩充,产生指定数目的实例.
2.在The Design Patterns Java Companion 一书中曾提到过用静态类的方式来实现 Singleton模式,并指出java.lang.Math就是一个例子,这里我并不表示赞同,因为Math并不是一个真正的对象,我们只是直接调用Math类所包装的静态方法而已,根本就没有创建实例的过程,又从何说起只产生一个实例呢?这个问题我曾到Javaranch的论坛上发过帖子,所有回帖的人也都是对这一观点持否定态度.
3.在多线程的程序中,singleton可能会变的不可靠,可能会出现多个实例,解决的办法很简单,加个同步修饰符: public static synchronized Singleton getInstance(). 这样就保证了线程的安全性.
4.最后要说的是大家可能会看见一些其他实现Singleton模式的方法,因为模式在具体的应用时是灵活的,不是一成不变的,并没有一个固定的做法,但大都是上面几种方法的变形.
关于单例模式就先搜集这些吧!嘿嘿!
|
转载于:https://blog.51cto.com/yin123/193676