Java单例模式的知识点详解

所属分类: 软件编程 / java 阅读数: 50
收藏 0 赞 0 分享

懒汉模式与饿汉模式

懒汉模式就是懒加载,用到的时候去加载,存在线程安全问题,需要手动地加锁控制。它的优点是类加载的速度比较快,按需加载,节省资源。

饿汉模式就是在类加载的时候会创建出实例。它天生就不存在线程安全问题。但是类加载的速度会变慢且耗费资源。

懒汉模式-单重检查

示例代码如下:

public class LazySingleton {

  private static LazySingleton singletoninstance = null;
  private Object data = new Object();

//私有化构造方法
  private LazySingleton(){

  }
//加锁访问
  public static synchronized LazySingleton getInstance(){

    if(singletoninstance == null){
      singletoninstance = new LazySingleton();
    }
    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

测试代码如下:

public class TestThread extends Thread {

  @Override
  public void run() {

    LazySingleton instance = LazySingleton.getInstance();
    System.out.println(instance.getData());
  }
}

public static void main(String[] args) {

    for(int i =0;i < 10;i++){
      TestThread t = new TestThread();
      t.start();
    }
  }
}

运行结果如下:

java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64

打印出同一个object对象,表明是从同一个LazySingleton对象中获取的数据。

但是上述代码存在一个显著的问题:多个线程同时访问getInstance()方法都是排队式的,即使该instance已经被创建的情况下。然而,如果该instance已经被创建,是可以支持并发访问的。需要对锁的控制细粒度化。

懒汉模式-双重检查

public class LazySingleton {
//声明为volatile变量
  private static volatile LazySingleton singletoninstance = null;
  private Object data = new Object();

  private LazySingleton(){

  }

  public static synchronized LazySingleton getInstance(){

    if(singletoninstance == null){
      synchronized (LazySingleton.class) {
        //这个第二重的的检查是必要的
        if(singletoninstance == null)
          singletoninstance = new LazySingleton();
      }
    }
    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

第二重检查是为了防止:

线程A发现instance未被创建,于是申请锁,进入临界区创建instance;于此同时另一个线程也发现instance未被创建,于是也要申请锁去创建instance,问题就这样发生了。而且,这个instance变量要被声明为volatile,也就是其中一个线程对它就行修改之后(也就是实例化),这一修改立马对其他线程可见,避免了无谓的等待。

检查代码同上,运行结果同上。

饿汉模式

public class HungerSingleton {

  private static final HungerSingleton singletoninstance = new HungerSingleton();
  private Object data = new Object();

  private HungerSingleton(){

  }

  public static HungerSingleton getInstance(){

    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }

}

在加载该类的时候就立马去实例化instance,不存在线程安全问题(由jvm保证线程安全问题),但是存在资源浪费、加载速度慢的问题。

检查代码同上,运行结果同上。

Holder模式

就是利用一个静态内部类来实现instance的实例化。这里利用了静态内部类的一个特性:该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载

public class HolderSingleton {

  private Object data = new Object();

  private HolderSingleton(){

  }
  
  private static class InnerClass{

    private static HolderSingleton singletoninstance = new HolderSingleton();
  }

  public static HolderSingleton getInstance(){

    return InnerClass.singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

测试代码同上,运行结果同上。

在加载InnerClass的时候才会去实例化这个instance,实现了延迟加载,并且同饿汉模式一样,由jvm保证线程安全。这种方法值得推荐。

应用场景:

在整个系统中,只允许共用一个实例的类适合用单例模式来实现,比如:

网站的计数器,只允许存在一个计数器实例;

线程池,只允许存在一个线程池对象;

连接池,只允许存在一个连接池对象;

知识点扩充

1.为什么要使用单例模式?

在我们日常的工作中,很多对象通常占用非常重要的系统资源,比如:IO处理,数据库操作等,那我们必须要限制这些对象只有且始终使用一个公用的实例,即单例。

2.单例模式的实现方式

构造函数私有化,防止其他类生成唯一公用实例外的实例。且单例类应该被定义为final,也就是说单例类不能被继承,因为如果允许继承那子类就都可以创建实例,违背了类唯一实例的初衷。

类中一个静态变量来保存单实例的引用。

一个共有的静态方法来获取单实例的引用。
3.单例模式的UML类图

4.单例模式的经典实现方式

  • 饿汉式:一开始就创建好实例,每次调用直接返回,经典的“拿空间换时间”。
  • 懒汉式:延迟加载,第一次调用的时候才加载,然后返回,以后的每次的调用就直接返回。经典“拿时间换空间”,多线程环境下要注意解决线程安全的问题。
  • 登记式:对一组单例模式进行的维护,主要是在数量上的扩展,通过线程安全的map把单例存进去,这样在调用时,先判断该单例是否已经创建,是的话直接返回,不是的话创建一个登记到map中,再返回。

以上就是本次脚本之家小编结合多篇整理的相关内容,希望能够帮助到大家。

更多精彩内容其他人还在看

利用MultipartFile实现文件上传功能

这篇文章主要为大家详细介绍了利用MultipartFile实现文件上传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Java编程实现NBA赛事接口调用实例代码

这篇文章主要介绍了Java编程实现NBA赛事接口调用实例代码,具有一定参考价值,需要的朋友可以了解下。
收藏 0 赞 0 分享

Java编程之双重循环打印图形

这篇文章主要介绍了Java编程之双重循环打印图形,属于Java编程基础练习部分,具有一定参考价值,需要的朋友可以了解下。
收藏 0 赞 0 分享

java基础学习JVM中GC的算法

这篇文章主要介绍了java基础学习JVM中GC的算法,通过图文加深对GC算法思路的理解。
收藏 0 赞 0 分享

Java编程Post数据请求和接收代码详解

这篇文章主要介绍了Java编程Post数据请求和接收代码详解,涉及enctype的三种编码,post与get等相关内容,具有一定参考价值,需要的朋友可以了解下。
收藏 0 赞 0 分享

Retrofit+Rxjava实现文件上传和下载功能

这篇文章主要介绍了Retrofit+Rxjava实现文件上传和下载功能,文中提到了单文件上传和多文件上传及相关参数的请求,需要的朋友参考下吧
收藏 0 赞 0 分享

Retrofit+Rxjava下载文件进度的实现

这篇文章主要介绍了Retrofit+Rxjava下载文件进度的实现,非常不错,具有参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

java检查服务器的连通两种方法代码分享

这篇文章主要介绍了java检查服务器的连通两种方法代码分享,涉及ping的介绍以及检查服务器连通的两种方法代码示例,具有一定参考价值,需要的朋友可以了解下。
收藏 0 赞 0 分享

Java/Android 获取网络重定向文件的真实URL的示例代码

本篇文章主要介绍了Java/Android 获取网络重定向文件的真实URL的示例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

java并发编程之同步器代码示例

这篇文章主要介绍了java并发编程之同步器代码示例,分享了相关代码,具有一定参考价值,需要的朋友可以了解下。
收藏 0 赞 0 分享
查看更多