单例模式5种实现方式_foolishAndStupid的博客-程序员秘密

技术标签: github  设计模式  

       在github上看到一个项目,实现了多种设计模式,就把它fork下来,一个一个看,然后也可以学习参考别人写的代码。
地址:https://github.com/iluwatar/java-design-patterns
(以下代码都转自上面的项目)
       单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

       实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
       
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
参考:wikipedia

常见的是

1.静态内部类(也叫饿汉模式)
2.懒汉模式
3.双重锁保证线程安全

还有另外3种写法:

4.序列化实现
5.枚举实现

1.静态内部类

//也叫饿汉模式,线程安全
public final class IvoryTower {
    

  /**
   * 静态变量属于类,不属于任何独立的对象,所以无需创建类的实例就可以访问静态变量。之所以会产生这样的结果,是因为编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然有多个实例,但这些实例共享该内存
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * 私有化构造器,使得不能在外部调用构造器,也就不能在外部使用
   IvoryTower it =new IvoryTower();的方式创建对象,保证了实例化对象只有一个
   */
  private IvoryTower() {}

  /**
   * 用来返回实例化对象
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}

2.懒汉模式
       和上面的不同之处在于没有在类加载的时候就创建对象,而是在真正要使用到这个对象的时候再去获取,优点在于如果这个创建对象过程很费时,那么一开始就创建会浪费较多时间,比如几分钟或者几个小时,万一创建好之后还用不到,那不是坑爹~~ 还有就是比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。于是有了懒加载。

public final class ThreadSafeLazyLoadedIvoryTower {
    

  private static ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {}

  /**
   * 
   */
  public  static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {

    if (instance == null) {   //(1)
                                //(2)
      instance = new ThreadSafeLazyLoadedIvoryTower();//(3)          
    }

    return instance;
  }
}

       注意getInstance()方法处的synchronized关键字,如果没有这个关键字,这个类就是线程不安全的。
懒汉模式在多线程环境下是不安全的,为什么这么说呢?假设现在有线程A,线程B,然后在线程A执行到代码(1)处之后,在代码(3)之前由于线程调度使得A暂停运行,一直停留在(2)处。这时候,如果线程B也执行到了代码(1)处的判断,由于线程A还尚未new对象就被暂停了,使得现在的instance还是null,所以B进行判断之后也会得到instance=null,然后也进入了代码(2),(3)处,顺利的new了一个对象。这时候如果线程A恢复运行,又会new一个对象,就导致了不止一个实例化对象。 但是如果在方法前加了一个synchronized关键字,在多线程环境下,它会使这个方法同时只被一个线程调用,那样就不存在线程A执行到一半被暂停转而被线程B执行,导致最后实例化了2个对象。
       但是思考一下,其实只需要在第一次实例化对象的时候才需要锁,在之后的调用的时候是不需要的,而上面这种写法使得每次调用都是加锁。优化方法就是双重锁,什么意思呢?就是对方法不加synchronized,但是在第一次判断instance==null,如果为null,再加锁,这样就使得加锁过程只会执行一次,一旦被执行过一次,instance!=null之后,就不会再次执行这个锁,就可以提高点效率!

public final class ThreadSafeDoubleCheckLocking {
    

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /**
   * private constructor to prevent client from instantiating.
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284
    ThreadSafeDoubleCheckLocking result = instance;
    if (result == null) {
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        result = instance;
        if (result == null) {
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

还有2种是序列化的方式和枚举方式
枚举方式:

public enum EnumIvoryTower {
  INSTANCE;
  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}

       重写toString()只是为了测试的时候方便看到结果。枚举的方式确实简单明了。默认枚举实例的创建是线程安全的,所以不需要担心线程安全的问题。但是在枚举中的其他任何方法的线程安全由程序员自己负责。还有防止上面的通过反射机制调用私用构造器。
序列化的方式:

public final class InitializingOnDemandHolderIdiom implements Serializable {
    

  private static final long serialVersionUID = 1L;

  private InitializingOnDemandHolderIdiom() {}

  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  protected Object readResolve() {
    return getInstance();
  }

  private static class HelperHolder {
    
    public static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }

}

       序列化是Java一个强大的功能,它可以将类的信息变成字节的形式存储在硬盘上或者通过socket传输。如果我们将一个单例类序列化,传输,再进行反序列化,那么如何能保证我们多次反序列化之后只会得到同一个实例化对象呢?
       readResolve()方法就是用来保证这一点的!而且这份实例代码里面用了一个静态内部类,然后在这个类里面初始化了一个静态对象。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/FoolishAndStupid/article/details/51232311

智能推荐

堆积排序-堆排序-heap sort_CaryaLiu的博客-程序员秘密

堆积排序是另一种形式的选择排序。它涉及到 堆积 和 完全二叉树 的概念。1. 堆积的定义具有n个数据元素的序列 K = (k1, k2, k3, k4, . . . , kn); 当且仅当满足条件k[ i ] >= k[ i*2 ] && k[ i ] >= k[ i*2+1] 或者k[ i ] i = (1, 2, 3, 4, . . . , n/2)时称序列K为一个

ExtJs常用布局--layout详解_unichart 的layoutconfig 的anchor 属性的用法_yijianzhi77的博客-程序员秘密

序言:笔者用的ExtJs版本:ext-3.2.0ExtJs常见的布局方式有:border、form、absolute、column、accordion、table、fit、card、anchor另外,不常见的布局有:tab、vbox、hbox本文所有实例代码已提供下载,下载链接:ExtJs常用布局--...

数据库系统概论期末总结(核心考点)_盖世英雄来了的博客-程序员秘密

这学期学习了数据库系统概论,不出意外的上课没好好听讲,转眼期末了,又到了熬夜爆肝的时刻,根据老师画的考试重点和难点,进行了总结和分析,希望能够加深自己的印象,同时也给其他人提供一点帮助.一.SQL语句学习数据库我们首先就应该学会使用它,如果不能上机测试的话,那就考察sql语句吧!!!在考试之中最重要的也是最好拿分的就是sql语句的增删改查,不需要死记硬背,只要稍微练习,能够清楚就可以了...

你不得不了解的前后端分离原理_前后端分离的原理_Java运输车的博客-程序员秘密

前后端分离已成为互联网项目开发的业界标准使用方式,通过nginx+tomcat的方式(也可以中间加一个nodejs)有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,车载终端,安卓,IOS等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。核心思想是前端html页面通过ajax调用后端的restuful api接口并使用json数据进行交互。在互联网架构中,名词解释:Web服务器:一般指像nginx,apache这

【论文翻译】PSENet:Shape Robust Text Detection with Progressive Scale Expansion Network_PRIS-SCMonkey的博客-程序员秘密

Shape Robust Text Detection with Progressive Scale Expansion Network基于渐进式尺寸可扩展网络的形状鲁棒文本检测论文链接:https://arxiv.org/pdf/1806.02559.pdf论文代码:https://github.com/whai362/psenet【摘要】 形状鲁棒性文本检测面临的挑战主要有两个方面:1...

详解图的各种令人心慌的概念和四种图的存储结构(整理到吐)_v1的出度怎么看_「已注销」的博客-程序员秘密

1.图的基本概念 2. 3.1.图的基本概念1.1无向图有向图1.无向图:如果任意两个顶点之间的边都是无向边,那么该图称为无向图2.有向图:如果任意两个顶点之间的边都是有向边,那么该图称为有向图无向图:有向图:1.2完全有向图完全无向图完全图:若图中各个顶点都与除自身外的其他顶点有关系,这样的无向图称为完全图,完全图分为完全有向图和完全无向图完全无向图:完全有向图:在n个顶点的无向完全图中,有n(n-1)/2条边。在n个顶点的有向完全图中有n(n-1)条边1.

随便推点

数学之美-读后感想_数学之美读后感_程序猿-黄某某的博客-程序员秘密

背景介绍   今年以来,对于大数据十分感兴趣,故花了些钱报了个线上大数据培训课程。陆陆续续有老师和同学推荐数学之美这本书,看过之后确实不错,特别是将今年来的一些热点,如自然语言处理,语音识别等数学原理深入浅出的表述,使得像我们这些对数学不太懂又像了解的人大有裨益。第一章 文字和语言 vs 数字和信息 第二章 自然语言处理 从规则到统计   第一章主要阐述了信息的重要性,语言和数字的产生都是...

Error querying database. Cause: java.sql.SQLSyntaxErrorException: ORA-00904: “A“.“AGE“:_maybezh的博客-程序员秘密

从上面报错信息来看,我以为是我mapper.xml文件中的<select></select>中的查询语句中的age字段写成了AGE,检查发现并不是!!!解决办法:这是因为Oracle数据库中创表时,表的字段名应该全是大写,或者全是小写。我的STUDENT表的学号字段写的stuID,最后一个字段是age,所以上面报错是用的A.AGE,在数据库中改一下就行了。(顺带说一句:Oracle中创建表要用大写字母命名!!!我因为用小写字母,找了2天错,太浪费时间了。)...

CannotResolveClassException: kg.apc.jmeter.threads.SteppingThreadGroup解决方法_秋9的博客-程序员秘密

【现象】打开jmx文件报如下错误具体错误信息如下:Problem loading XML from:'C:\***压测.jmx'.Cause:CannotResolveClassException: kg.apc.jmeter.threads.SteppingThreadGroupDetail:com.thoughtworks.xstream.converters.Conv...

selenium的select类实现下拉列表的定位_永远不要矫情的博客-程序员秘密

selenium中的一个工具类select主要用于处理下拉列表,常用的方法如下:在这里,我们使用了和下面python文件同目录的form3.html,代码如下所示:<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>title</title></head><body><form> provi

链表(上):如何实现LRU缓存淘汰算法?_yaojie·future的博客-程序员秘密

链表(上)链接:https://time.geekbang.org/column/article/41013一、什么是链表?1.和数组一样,链表也是一种线性表。2.从内存结构来看,链表的内存结构是不连续的内存空间,是将一组零散的内存块串联起来,从而进行数据存储的数据结构。3.链表中的每一个内存块被称为节点Node。节点除了存储数据外,还需记录链上下一个节点的地址,即后继指针next。二...

linux下Komodo安装日志_weixin_33805743的博客-程序员秘密

官方下载地址komodo-edit (免费)komodo-ide(收费,去找个注册机破接下。。。)    ==============================================================================Komodo IDE 6 has been successfully installed ...