资讯专栏INFORMATION COLUMN

Java 设计模式之单例模式

everfight / 3393人阅读

摘要:在设计模式一书中,将单例模式称作单件模式。通过关键字,来保证不会同时有两个线程进入该方法的实例对象改善多线程问题为了符合大多数程序,很明显地,我们需要确保单例模式能在多线程的情况下正常工作。

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

一、了解单例模式

1.1 什么是单例模式

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

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

1.2 单例模式 UML 图解

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 部分知识总结

单例模式确保程序中一个类最多只有一个实例。单例模式也提供访问这个实例的全局点。

如果你使用多个类加载器,可能导致单例模式失效,从而产生多个实例。

确定性能和资源上的限制,我们应当选择合适的方案来实现单例模式。

参考资料

《Head First 设计模式》

https://www.cnblogs.com/tufujie/p/5614682.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77363.html

相关文章

  • Java基础学习——多线程单例设计模式(转)

    摘要:总之,选择单例模式就是为了避免不一致状态,避免政出多头。二饿汉式单例饿汉式单例类在类初始化时,已经自行实例化静态工厂方法饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。 概念:  Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍两种:懒汉式单例、饿汉式单例。  单例模式有以下特点:  1、单例类只能有一个实例。 ...

    dendoink 评论0 收藏0
  • JavaScript设计模式单例模式

    摘要:原文博客地址单例模式系统中被唯一使用,一个类只有一个实例。中的单例模式利用闭包实现了私有变量两者是否相等弱类型,没有私有方法,使用者还是可以直接一个,也会有方法分割线不是单例最简单的单例模式,就是对象。 原文博客地址:https://finget.github.io/2018/11/06/single/ 单例模式 系统中被唯一使用,一个类只有一个实例。实现方法一般是先判断实例是否存在,...

    lk20150415 评论0 收藏0
  • 优才公开课笔记:php设计模式(一) 单例模式

    摘要:最近开展了三次设计模式的公开课,现在来总结一下设计模式在中的应用,这是第一篇创建型模式之单例模式。不过因为不支持多线程所以不需要考虑这个问题了。 最近开展了三次设计模式的公开课,现在来总结一下设计模式在PHP中的应用,这是第一篇创建型模式之单例模式。 一、设计模式简介 首先我们来认识一下什么是设计模式: 设计模式是一套被反复使用、容易被他人理解的、可靠的代码设计经验的总结。 设计模式不...

    guyan0319 评论0 收藏0
  • Java 23种设计模式 单例模式 7种实现方式

    摘要:一懒汉式线程不安全懒汉式线程不安全私有构造方法只允许在内部进行实例的创建创建实例二懒汉式线程安全懒汉式线程安全私有构造方法只允许在内部进行实例的创建创建实例线程安全三饿汉式线程安全饿汉式私有构造方法只允许在内部进行实例的创建静态初始化由保证 一、懒汉式(线程不安全) package com.java.singleton; //懒汉式 线程不安全 public class LazySi...

    IntMain 评论0 收藏0
  • 设计模式系列单例模式

    摘要:下面我们来看看看中的单例模式,中使用的是单例注册表的特殊方式实现的单例模式,所以说模式是死的,需要灵活得运用。 本文循序渐进介绍单例模式的几种实现方式,以及Jdk中使用到单例模式的例子,以及sring框架中使用到的单例模式例子。 饿汉式 package signgleton; /** * 单例模式简单的实现 */ public class Singleton { priv...

    Jason 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<