Java中单例模式的使用

2023-05-16

什么是单例模式

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。


单例模式的实例

Java中案例模式的构建方法有两种,一种是懒汉模式,一种是饿汉模式。

  • 懒汉模式,指全局的单例模式在第一次被使用时构建;

  • 饿汉模式,只全局的单例模式在类装载时构建;

我们来分别看一下Java中单例模式的实例

饿汉模式的代码如下

public class Singleton{

    private static final Singleton INSTANCE = new Singleton;

    // Private constructor suppresses
    private Singleton(){

    // default public constructor
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

可见饿汉模式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

懒汉模式的代码如下

线程不安全:

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

线程安全:

public class Singleton{

    private static final Singleton INSTANCE = null;

    // Private constructor suppresses
    // default public constructor
    private Singleton(){
    }

    //thread safe and performance  promote 
    public Singleton getInstance(){
        if(INSTANCE == null){
            synchronized(Singleton.class){
//when more than two threads run into the first null check same time, to avoid instanced more than one time, it needs to be checked again.
                if(INSTANCE == null){ 
                     INSTANCE = new Singleton();
                  }
            }
        }
        return INSTANCE;
    }
}

可见懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间

由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式实现呢?

枚举

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,在Java枚举类中有详细介绍枚举的线程安全问题和序列化问题,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏

    public enum Singleton{

        INSTANCE;
        public void whateverMethod(){
        }
    }

双重检查加锁

可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?

所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。

public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //先检查实例是否存在,如果不存在才进入下面的同步块
        if(instance == null){
            //同步块,线程安全的创建实例
            synchronized (Singleton.class) {
                //再次检查实例是否存在,如果不存在才真正的创建实例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。

提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。

根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能实现线程安全呢?

Lazy initialization holder class模式

这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。

内部级类的基本知识

简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。

类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。

类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。

类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。

多线程缺省同步锁的知识

大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时

2.访问final字段时

3.在创建线程之前创建对象时

4.线程可以看见它将要处理的对象时

怎样既能实现延迟加载,又能实现线程安全

要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。

如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。

public class Singleton {

    private Singleton(){}
    /**
     *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

使用单例模式的意义

在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如网站首页页面缓存)。

避免对资源的多重占用(比如写文件操作)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中单例模式的使用 的相关文章

随机推荐

  • 为什么指针变量做形参可以改变实参的数据

    形参不能传任何东西给实参 xff0c 实参传过去的东西都是一个副本 下面以一个交换数据的被调函数片段为例 在指针变量由实参传递给形参时传过去的实际是指针变量的值 xff0c 即一个地址 xff0c 在 t 61 p1 p1 61 p2 p2
  • 敲线性表代码时遇到的问题(C++)【exit,return】

    1 xff1a exit OVERFLOW exit简介 为C 43 43 的退出函数 xff0c 声明于stdlib h中 xff0c 对于C 43 43 其标准的头文件为cstdlib 声明为 void exit int value e
  • /etc目录详解

    Linux etc目录详解 etc目录 包含很多文件 许多网络配置文件也在 etc 中 etc rc or etc rc d or etc rc d 启动 或改变运行级时运行的scripts或scripts的目录 etc passwd 用户
  • Tomcat 8.0 Mac 安装与配置

    工具 原料 1 xff0c JDK xff1a 版本为jdk 8u40 macosx x64 dmg 下载地址http www oracle com technetwork java javase downloads jdk8 downlo
  • 2011届移动开发者大会

    2011年11月4号星期五 xff0c 早晨八点我们就早早的来到了会场 xff0c 因为有了上次云计算大会的经验 xff0c 所以我们早早的就来了 xff0c 因为人很多我们必须才能找到一个比较好的位置 由于来的太早工作人员很多都没有就位
  • 第五篇 openvslam建图与优化模块梳理

    建图模块 mapping module在初始化系统的时候进行实例化 xff0c 在构建实例的时候会实例化local map cleaner和local bundle adjuster 系统启动的时候会在另外一个线程中启动该模块 code s
  • 个人安卓学习笔记---java.io.IOException: Unable to open sync connection!

    在使用手机调试程序的时候出现了java io IOException Unable to open sync connection 这样的异常 xff0c 我尝试使用拔掉USB然后重新 xff0c 插入 xff0c 结果失败 再尝试 xff
  • "_OBJC_CLASS_$_Play", referenced from:

    IOS做了这么久也没写过什么博客 xff0c 不好不好 xff0c 今天开始写 遇到的问题 xff1a 34 OBJC CLASS Play 34 referenced from 解决方案 xff1a Tagert Build Phases
  • 树莓派SSH远程连接连接失败的解决办法

    树莓派SSH远程连接 将全新的树莓派系统烧录 xff0c 开机然后用SSH远程连接 xff0c 结果SSH连接提示 connection refused xff0c 导致连接树莓派失败 出现错误的原因是自 2016 11 25 官方发布的新
  • 在树莓派中安装ROS系统(Kinetic)

    在树莓派中安装ROS系统 重新梳理了一下树莓派的安装流程 xff0c 现在我们来开始吧 打开官网教程 http wiki ros org kinetic step1 安装源 xff08 中国 xff09 sudo sh c 39 etc l
  • ROS学习笔记-roscd指令

    对ROS文件系统而言 xff0c ROS中的roscd命令实现利用包的名字直接切换到相应的文件目录下 xff0c 命令使用方法如下 xff1a span class hljs tag roscd span span class hljs a
  • configure it with blueman-service

    用下面这个命令把Linux目录的名字由中文改成英文了 export LANG span class hljs subst 61 span en US xdg span class hljs attribute user span span
  • 关于Ubuntu16.04升级系统后启动报错问题的修复

    关于Ubuntu16 04升级系统后启动报错问题的修复 Ubuntu16 04升级后启动报错为 Failed to start Load Kernel Modules 使用systemctl status systemd modules l
  • Ubuntu Mate 自动登录

    树莓派安装Ubuntu Mate 设置自动启动 需要修改文件 usr share lightdm lightdm conf d 60 lightdm gtk greeter conf sudo vim usr share lightdm l
  • Linux系统调用实现简析

    1 前言 限于作者能力水平 xff0c 本文可能存在谬误 xff0c 因此而给读者带来的损失 xff0c 作者不做任何承诺 2 背景 本篇基于 Linux 4 14 43 ARM 32 43 glibc 2 31 进行分析 3 系统调用的实
  • Docker中容器的备份、恢复和迁移

    1 备份容器 首先 xff0c 为了备份 Docker中的容器 xff0c 我们会想看看我们想要备份的容器列表 要达成该目的 xff0c 我们需要在我们运行着 Docker 引擎 xff0c 并已创建了容器的 Linux 机器中运行 doc
  • 关于OpenCV的那些事——相机姿态更新

    上一节我们使用张正友相机标定法获得了相机内参 xff0c 这一节我们使用 PnP Perspective n Point 算法估计相机初始姿态并更新之 推荐3篇我学习的博客 xff1a 姿态估计 Pose estimation algori
  • Java中接口(Interface)的定义和使用

    有关 Java 中接口的使用相信程序员们都知道 xff0c 但是你们知不知道接口到底有什么用呢 xff1f 毫无疑问 xff0c 接口的重要性远比想象中重要 接下来我们便一起来学习Java中接口使用 Java接口是什么 Java接口是一系列
  • Java中向下转型的意义

    什么是向上转型和向下转型 在Java继承体系中 xff0c 认为基类 xff08 父类 超类 xff09 在上层 xff0c 导出类 xff08 子类 继承类 派生类 xff09 在下层 xff0c 因此向上转型的意思就是把子类对象转成父类
  • Java中单例模式的使用

    什么是单例模式 单例模式 xff0c 也叫单子模式 xff0c 是一种常用的软件设计模式 在应用这个模式时 xff0c 单例对象的类必须保证只有一个实例存在 许多时候整个系统只需要拥有一个的全局对象 xff0c 这样有利于我们协调系统整体的