Java设计模式——单例模式详解

单例模式概述

也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

一、构建方式

通常单例模式在Java语言中,有两种构建方式:

  • 懒汉方式。指全局的单例实例在第一次被使用时构建。
  • 饿汉方式。指全局的单例实例在类装载时构建。

二、例子

在Java语言中,单例模式(饿汉模式)应用的例子:

/**
单例模式例子(饿汉模式)
*/
public class MathHelper {

    //类装载时构建实例对象,且指定为final类型
    private final static MathHelper mathhelper = new MathHelper();

    //私有化构造方法(不允许外部代码通过new关键字创建对象)
    private MathHelper() {

    }

    //主动暴露出的外部获取该类对象的方法
    public static MathHelper getInstance(){
        return mathhelper;
    }

    //工具类中要调用的方法
    public int add(int a,int b){
        return a + b;
    }
}

代码很简单,注释很清楚,我就不一一解释了。

下面来看懒汉模式的实现,对于一般的单线程的懒汉模式的单例模式实现如下:

/**
单线程的单例模式例子(懒汉模式)
*/
public class MathHelper {
    //类装载时不构建实例对象
    private static MathHelper mathHelper = null;
    //私有化构造方法(不允许外部代码通过new关键字创建对象)
    private MathHelper() {

    }
    //主动暴露出的外部获取该类对象的方法
    public static MathHelper getInstance(){
        //全局的单例实例在第一次被使用时构建
        if(mathHelper==null){
            mathHelper = new MathHelper();
        }
            return mathhelper;
    }

    //工具类中要调用的方法
    public int add(int a,int b){
        return a + b;
    }
}

特别注意:以上代码在单线程系统中调用没有问题,单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。

解决办法也很简单,就是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

多线程懒汉模式例子如下:

/**
多线程的单例模式例子(懒汉模式)
*/
public class MathHelper {
    //注意 mathHelper 被声明为 volatile
    private static volatile MathHelper mathHelper = null;
    //私有化构造方法(不允许外部代码通过new关键字创建对象)
    private MathHelper() {

    }
    //主动暴露出的外部获取该类对象的方法
    public static MathHelper getInstance(){
        if(mathHelper==null){
            synchronized(MathHelper.class){
                 //当两个线程都同时第一次检查mathHelper==null时,为了避免多次实例化,所以需要再次检查
                 if(mathHelper == null){ 
                     mathHelper= new mathHelper();
                 }
            }
        return mathhelper;
    }

    //工具类中要调用的方法
    public int add(int a,int b){
        return a + b;
    }
}

这样就可以完美的在多线程环境下实现单例模式了

三、适用范围

常见单例模式适用范围:
1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
9. HttpApplication 也是单位例的典型应用。熟悉ASP.NET(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

总之:
单例模式应用的场景一般发现在以下条件下:

(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

四、优缺点与总结

主要优点:
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
3、允许可变数目的实例。

主要缺点:
1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

总结:使用单例模式最核心的一点是体现了面向对象封装特性中的“单一职责”和“对象自治”原则。但也要考虑实际情况,具体问题具体分析!

参考链接:https://zh.wikipedia.org/wiki/单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值