Java设计模式系列之单例设计模式

栏目: 后端 · 发布时间: 6年前

内容简介:Hello,大家好,距离上次写博客是2018年1月26号,算了下,有8个月没写博客了。这里给大家道个歉,因为我换了工作,现就职在深圳一家公司,换了城市,加上工作上的一些事,所以一直抽不开身,2个月前不是太忙的时候,一直想着写点什么,可又找不到感觉了,所有就慢慢吞吞的,今天下定决心,写点渣渣也要写。由于博主最近准备把设计模式这一块好好整一整,所以就从最简单的所谓的单例模式,其实大家都知道,就是在应用程序中的某个类,无论在任何时间想拿到这个类的事例,都拿到的是唯一一个实例,那么问题来了,什么叫唯一的一个实例,

Hello,大家好,距离上次写博客是2018年1月26号,算了下,有8个月没写博客了。这里给大家道个歉,因为我换了工作,现就职在深圳一家公司,换了城市,加上工作上的一些事,所以一直抽不开身,2个月前不是太忙的时候,一直想着写点什么,可又找不到感觉了,所有就慢慢吞吞的,今天下定决心,写点渣渣也要写。由于博主最近准备把 设计模式 这一块好好整一整,所以就从最简单的 单例模式 开始,这个单例模式,说简单也简单,说难也难。有点生疏,大家看的时候,如果觉得写的不好,多多包含,OK,进入正题,文章结构:

  1. 单例模式简介
  2. 饿汉式单例模式
  3. 懒汉式线程安全单例模式

1. 单例模式简介

所谓的单例模式,其实大家都知道,就是在应用程序中的某个类,无论在任何时间想拿到这个类的事例,都拿到的是唯一一个实例,那么问题来了,什么叫唯一的一个实例,说的再通俗点,就是拿到的那个指针(C,C++中叫指针,Java中叫引用对象)地址是唯一的。大家通过 new 关键字new几次new出来的对象,那肯定不是唯一的了。

2. 饿汉式单例模式

上代码之前,先说一下,单例模式的几个要点:

  • 构造函数私有化(上面说过了,代码中new出来的地址不唯一,那肯定要私有化,不能让用户代码去new)
  • 单例类提供方法或者单例对象

OK,上代码了,因为太简单了,不知道怎么再展开了。

public final class Student {
  // 注意这里是私有化的
  private Student() {}
  // 注意这里是私有化的
  private static final Student INSTANCE = new Student();
 // 暴露出去的方法
  public static Student getInstance() {
    return INSTANCE;
  }
}
复制代码

贼鸡儿简单,想拿Student对象的时候,直接Student.getInstance(); 不存在什么线程安全问题,因为类内部的static变量会在类加载的时候直接创建出来。你要 想整个静态代码快去初始化INSTANCE变量 ,其实也是一样一样的。这里就不写了。其实就是利用是static变量和static代码快在类加载时直接加载执行原理。

3. 懒汉式线程安全单例模式

其实对于绝大多出场景,上面的饿汉已经绝对够用了。比如Spring框架中的bean,默认情况下就是单例的,就是直接给你new出来,然后丢在内存里,你要@Autowire的时候,直接给你。但这里有个小问题,有些类的初始化非常耗时,比如数据库链接,Redis链接等,这种网络IO操作。很有可能因为网络原因导致很耗时,在类被加载而他的实例还没有被使用的时候,上面的饿汉模式显然是不太合适的,如果这种耗时比较多的饿汉单例比较多的话,影响应用程序的启动时间。So,我们的懒汉上场了,OK,Code:

public final class Student {

  private Student() {}
  
  private static  Student INSTANCE = null;
  
  public static Student getInstance() {
   //Mark 1 
    if(INSTANCE==null){
        INSTANCE= new Student();
    }
    return INSTANCE;
  }
}
复制代码

代码也是贼鸡儿简单,在getInstance()中判断一下INSTANCE是不是null,如果是null(第一次调用)就初始化,下一次不是Null,返回旧的那个。写到这里,有点并发编程经验的小伙伴就知道了,在 Mark 1 的位置,当有多个线程同时进入的话,会有两个线程同时进入if()代码快,那么就糟糕了,INSTANCE会被初始化多次。不是线程安全的。接下来重头戏,我会演示几种线程安全的懒汉式单例模式:

(a) synchronized 方法 保证线程同步

直接上代码:

public final class Student {

  private Student() {}
  
  private static  Student INSTANCE = null;
  // Mark 2 
  public static synchronized Student getInstance() {
   //Mark 1 
    if(INSTANCE==null){
        INSTANCE= new Student();
    }
    return INSTANCE;
  }
}
复制代码

其实很简单,就是在getInstance()方法上加了synchronized关键字,这里synchronized关键字就不展开讲了,其实就是锁住了整个getInstance()方法,保证线程同步,这样当有多个线程进入这个方法时,会以Student.class为锁,只有一个方法先进去,然后后面的线程再进去的时候,INSTANCE已经不是null了,就进入不了if代码块。所以可以保证if代码块在多线程的情况下只进入一次,也就是说Student类只被实例化一次。

(b) synchronized 代码块 双重锁检查(推荐)

上面的synchronized锁住方法,其实是阔以的,但大家都知道synchronized关键字锁住方法的效率还是有点小问题的,毕竟每次调用这个方法都加锁,想想都很不爽 。效率不是很高,我就不多说了,所以这里推荐使用的是synchronized关键字的 双重锁检查 方式,代码如下:

public final class Student {

  private Student() {}

  // Mark 1 
  private static volatile  Student INSTANCE = null;

  public static Student getInstance() {
    // Mark 2 
    if(INSTANCE == null)
      synchronized (Student.class){
        // Mark 3 
        if(INSTANCE==null){
          INSTANCE= new Student();
        }
      }
    return INSTANCE;
  }
}
复制代码

好了,来分析下这个代码,先看所谓的双重锁检查的Mark 2 和 Mark 3 ,如果没有Mark 2 的话,其实和在方法上加synchronized关键字的效果是一样一样的,效率比较差,每次进这个方法后,都加一波锁。如果没有Mark 3 的话,大家想一想,当有两个线程同时走到Mark 2 的位置,这时两个线程都进入第一个If代码快,然后在synchronized关键字的时候被锁住一个线程,另一个进去了,然后INSTANCE被初始化了,那么当这个线程出来后,另外一个被锁住的线程进去之后,如果没有Mark 3 的话,直接执行INSTANCE= new Student(); 又实例化了一个Student,这显然不是单例模式了。所有,Mark 2 和 Mark 3 的位置的if判断都是不能少的,这也就是所谓的双重锁检查了。这样即能保证Student类只被实例化一次,又能保证在安全的实例化后,后续getInstance()的时候不走有锁的代码了,是不是很完美。大家好好体会一下。最后值得一说的是,在Mark 1 的位置,必须保证这个INSTANCE变量是volatile 类型的,其实是为了保证线程可见性,保证第一个进入线程后的赋值操作,在后面的线程进入后,能够看到,也就是说Mark 3 位置能看到。给大家一个参考文章,解释为什么要volatile关键字的。 单例模式中用volatile和synchronized来满足双重检查锁机制

(c ) 静态内部类方式

public final class Student {


  private Student() {}

  public static Student getInstance() {
  
        return StudentInner.INSTANCE;
  }

  private static class StudentInner {
  
    private static final Student INSTANCE =
        new Student();
  }
}
复制代码

其实就是利用了静态内部类的加载顺序问题,(静态内部类的加载顺序),只有在调用StudentInner.INSTANCE的时候,静态内部类才被加载,INSTANCE变量才会被实例化,而且,类的加载肯定是线程安全的,不用考虑volatile和synchronized的问题。有意思不?!哈哈。

4. 结语

好了,单例模式其实就差不多了,网上也有很多相似的文章,我其实就是做了个总结,加了点自己的理解,没什么技术含量,后面给大家写其他设计模式的时候可能有点技术含量,结合实际的案例,比如哪些框架里用到了,这个单例模式,最典型的就是Spring容器里的Bean了,要是再要举例的话,那就是System.getSecurityManager()了,这个也是单例的。这个类是管理Java Application的权限的,不怎么用,我们一般都是运行容器时管理权限,很少在代码里去控制。 Over,Have a good day !


以上所述就是小编给大家介绍的《Java设计模式系列之单例设计模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Coding the Matrix

Coding the Matrix

Philip N. Klein / Newtonian Press / 2013-7-26 / $35.00

An engaging introduction to vectors and matrices and the algorithms that operate on them, intended for the student who knows how to program. Mathematical concepts and computational problems are motiva......一起来看看 《Coding the Matrix》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具