Java 设计模式之单例模式

同步通
• 阅读 2193

在《Head First 设计模式》一书中,将单例模式称作单件模式。这里为了适应大环境,把它称之为大家更熟悉的单例模式。

一、了解单例模式

1.1 什么是单例模式

单例模式确保一个类只有一个实例,并提供一个安全访问点。

我们把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。想要获取单例实例,通过单例类是唯一的途径。单例类提供对这个实例的全局访问点:当你需要实例时,向类查询,它会返回单个实例。

1.2 单例模式 UML 图解

Java 设计模式之单例模式

1.3 单例模式应用场景

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。比如线程池、缓存、日志对象等。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及要求只有一个对象的场景。

二、单例模式具体应用

2.1 经典的单例模式实现

采用经典单例模式实现代码有一个特点:如果我们不需要这个实例 (调用 getInstance() 方法),它就永远不会产生。因此这种方式也被称为“延迟实例化”(lazy instantiaze)。也被大家称为“懒汉式”。

单例类 Singleton

package com.jas.singleton;

public class Singleton {
    // 用静态变量来记录 Singleton 类的唯一实例
    private static Singleton uniqueInstance;

    /**
     * 把构造器声明为私有的,只有自己 Singleton 内部才可以调用构造器
     */
    private Singleton(){}

    /**
     * getInstance() 方法来实例化对象
     * 
     * @return Singleton 的实例对象
     */
    public static Singleton getInstance(){
        if(uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        
        return uniqueInstance;
    }
}

测试类

package com.jas.singleton;

public class SingletonTest {
    public static void main(String[] args) {
        
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        
        System.out.println(singleton1 == singleton2);
    }
}

     /**
     * 输出
     * true
     */

这虽然是经典的单例模式,但是这样做却存在着一个严重的问题:当多个线程同时访问 getInstance() 方法时,会产生线程安全问题,可能导致产生的实例可能会有多个,这样就违反了单例的原则。

2.2 处理多线程

存在线程安全问题,我们的第一反应可能是加同步锁。就像下面这样,这样做是可以解决线程安全问题,但是却降低了性能。因为只有在第一次执行该方法的时候,才真正需要同步。之后再调用此方法,同步反而会成为一种累赘。

    /**
     * 通过 synchronized 关键字,来保证不会同时有两个线程进入该方法
     * 
     * @return Singleton 的实例对象
     */
    public synchronized static Singleton getInstance(){
        if(uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        
        return uniqueInstance;
    }

2.3 改善多线程问题

为了符合大多数 Java 程序,很明显地,我们需要确保单例模式能在多线程的情况下正常工作。但是同步的做法会击垮其性能,所以提供以下几种方法来解决问题。

(1) 直接同步

直接同步虽然会降低性能,但是如果你的程序可以承受 getInstance() 造成的额外代价,同步确实是一种既简单又有效的方法。但是你必须知道,同步一个方法,可能会使程序的执行效率下降几十倍。因此,如果你需要频繁使用单例对象,那么你就要重新考虑设计了。

(2) “急切”创建实例

如果应用程序总是创建并使用单例创建的对象,或者在创建和运行时方面的负担不太严重,你可以急切 (early) 创建此对象。这种方式也被大家称为“恶汉式”。就像下面这样

package com.jas.singleton;

public class Singleton {
    //在静态初始化器中创建对象,用来保证线程安全
    private static Singleton uniqueInstance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

利用上面这种做法,我们依赖 JVM 在加载这个类时马上创建此唯一的实例。JVM 保证在任何时候任何线程访问 getInstance() 方法之前,一定会先创建此实例。这样一来就可以解决多线程之间的安全问题。

(3)双重检验加锁

利用双重检验加锁 (double-checked locking),首先检查实例是否已经被创建了,如果未创建,“才”开始同步。这样一来,只有第一次会同步,这样做正是我们想要的。

package com.jas.singleton;

public class Singleton {
    //volatile 关键字用来保证内存可见性,使多线程正确处理 uniqueInstance 对象
    private static volatile Singleton uniqueInstance;

    private Singleton(){}

    public static Singleton getInstance(){
        //使用这种方式,只有第一次才会彻底访问并执这里的代码
        if(uniqueInstance == null){     //检查实例,如果不存在进入同步区
            synchronized (Singleton.class){
                if(uniqueInstance == null){     //进入同步区后,再检查一次。如果为 null,才开始创建实例
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

如果你性能是你关心的重点,那么这种方式会帮你大大减少访问 getInstance() 时的时间消耗。需要在注意的是:这种双重检验加锁的方式并不适用于 1.4 及之前更早的版本。

三、单例模式总结

3.1 优缺点总结

优点

  • 实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
  • 灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点

  • 开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例。
  • 可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
  • 对象生存期:不能解决删除单个对象的问题。在提供内存管理的语言,只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。

3.2 部分知识总结

  • 单例模式确保程序中一个类最多只有一个实例。单例模式也提供访问这个实例的全局点。
  • 如果你使用多个类加载器,可能导致单例模式失效,从而产生多个实例。
  • 确定性能和资源上的限制,我们应当选择合适的方案来实现单例模式。

参考资料

点赞
收藏
评论区
推荐文章
3A网络 3A网络
3年前
Golang 常见设计模式之单例模式
之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非
Wesley13 Wesley13
4年前
java设计模式1
1:单例模式简介  单例模式是一种常用的软件设计模式,它确保某个类只有一个实例,而且自行实例化并向整个系统提供唯一的实例。总而言之就是在系统中只会存在一个对象,其中的数据是共享的  特点:    单例类只能有一个实例,所以一般会用static进行修释。    单例类必须自己创建自己的唯一实例。也就是在类中要new一个自己。    单例类必
Wesley13 Wesley13
4年前
java 23种设计模式(五、单例模式)
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。单例模式的结构  单例模式的特点:单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。  饿汉式单例类publicclassEagerSingleton
Wesley13 Wesley13
4年前
java中饿汉与懒汉的故事(单例设计模式)
java中的单例设计模式关于设计模式,这其实是单独存在的东西,它不属于java,但是在java中使用较多,所以今天我就给大家介绍下单例设计模式中的饿汉和懒汉这俩朴素的打工人。首先我先说明下单例设计模式是啥(如果不想了解,可以直接划下去看饿汉和懒汉):类的单例设计模式就是采用一定的方法保证在整个软件系统中,对某个类只能存在一
红烧土豆泥 红烧土豆泥
4年前
创建型模式之单例设计模式
什么是单例设计模式?顾名思义,只有一个实例。单例模式它主要是确保一个类只有一个实例,并且可以提供一个全局的访问点。废话少说,直接上干货了单例模式之饿汉式所谓饿汉式,顾名思义,“它很饿”。所以说,它一旦被加载进来,就会直接实例化一个对象。例如:languageclassSingleton{privatestaticfin
Wesley13 Wesley13
4年前
JAVA设计模式之单例设计模式
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。  在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:  1.限制类实例化对象。即只能产生一个对象。
Wesley13 Wesley13
4年前
Java单例模式
什么是单例模式  单例模式是在程序中,一个类保证只有一个实例,并提供统一的访问入口。为什么要用单例模式节省内存节省计算如对象实例中的一样的,那就不用每次都创建一个对象方便管理因为单例提供一个统一的访问入口,不需要创建N多个对象,很多工具类都用了单例实现,如日志、字符串工具类
Wesley13 Wesley13
4年前
PHP单例模式(精讲)
首先我们要明确单例模式这个概念,那么什么是单例模式呢?单例模式顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类我们称之为单例类。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
Stella981 Stella981
4年前
C#设计模式(1)——单例模式(Singleton)
单例模式即所谓的一个类只能有一个实例,也就是类只能在内部实例一次,然后提供这一实例,外部无法对此类实例化。单例模式的特点:1、只能有一个实例;2、只能自己创建自己的唯一实例;3、必须给所有其他的对象提供这一实例。普通单例模式(没有考虑线程安全)  ///<summary///单例模式
Wesley13 Wesley13
4年前
(面试常问)4种单例设计模式的总结(内含代码以及分析)
单例设计模式:  单例模式,是一种常见的软件设计模式.在它的核心结构中只包含了一个被称为单例的特殊类.通过单例模式可以保证系统中只有该类的一个实例对象.优点:  实例控制:单例模式会阻止其它对象实例化其自己的单例对象的副本,从而确保所有对象都访问的是唯一的实例   灵活性:因为类控制了实例化过程,所以类可以很灵活的更改实
Wesley13 Wesley13
4年前
23种设计模式(1):单例模式
定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。类型:创建类模式类图:!23种设计模式(1):单例模式第1张|快课网(http://static.oschina.net/uploads/img/201407/05200605_0dij.gif"23种设计模式(1):单例模式