Java 单例模式:懒加载(延迟加载)和即时加载

Java 单例模式:懒加载(延迟加载)和即时加载

引言

在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制(或懒加载、延时加载),也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用。这个机制在单线程环境下的实现非常简单,然而在多线程环境下却存在隐患。

1、单例模式的惰性加载

通常当我们设计一个单例类的时候,会在类的内部构造这个类(通过构造函数,或者在定义处直接创建),并对外提供一个static getInstance() 方法提供获取该单例对象的途径。

public class Singleton        
{        private static Singleton instance = new Singleton();        private Singleton(){}        public static Singleton getInstance(){        return instance;         }        
} 

这样的代码缺点是:第一次加载类的时候会连带着创建 Singleton 实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton 实例的创建非常消耗系统资源,而应用始终都没有使用 Singleton 实例,那么创建 Singleton 消耗的系统资源就被白白浪费了。

为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。

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

2、惰性加载在多线程中的问题

先将惰性加载的代码提取出来:

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

这是如果两个线程 A 和 B 同时执行了该方法,然后以如下方式执行:

  • A 进入 if 判断,此时 foo 为 null,因此进入 if 内
  • B 进入 if 判断,此时 A 还没有创建 foo,因此 foo 也为 null,因此 B 也进入 if 内
  • A 创建了一个 Foo 并返回
  • B 也创建了一个 Foo 并返回
    此时问题出现了,我们的单例被创建了两次,而这并不是我们所期望的。

3. 各种解决方案及其存在的问题

3.1 使用 Class 锁机制
以上问题最直观的解决办法就是给 getInstance 方法加上一个 synchronize 前缀,这样每次只允许一个现成调用 getInstance 方法:

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

这种解决办法的确可以防止错误的出现,但是它却很影响性能:每次调用 getInstance 方法的时候都必须获得 Singleton 的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了

3.2 double-checked locking
曾经有人为了解决以上问题,提出了 double-checked locking 的解决方案

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

让我们来看一下这个代码是如何工作的:首先当一个线程发出请求后,会先检查 instance 是否为null,如果不是则直接返回其内容,这样避免了进入 synchronized 块所需要花费的资源。其次,即使第2节提到的情况发生了,两个线程同时进入了第一个 if 判断,那么他们也必须按照顺序执行 synchronized 块中的代码,第一个进入代码块的线程会创建一个新的Singleton实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。

上述描述似乎已经解决了我们面临的所有问题,但实际上,从 JVM 的角度讲,这些代码仍然可能发生错误。

对于 JVM 而言,它执行的是一个个 Java 指令。在 Java 指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是 JVM 并不保证这两个操作的先后顺序,也就是说有可能 JVM 会为新的 Singleton 实例分配空间,然后直接赋值给 instance 成员,然后再去初始化这个 Singleton 实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:

  • A、B线程同时进入了第一个if判断
  • A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  • 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  • B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  • 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

4. 通过内部类实现多线程环境中的单例模式

为了实现慢加载,并且不希望每次调用 getInstance 时都必须互斥执行,最好并且最方便的解决办法如下:

public class Singleton{        private Singleton(){}        private static class SingletonContainer{        private static Singleton instance = new Singleton();        }        public static Singleton getInstance(){        return SingletonContainer.instance;        }        
}   

JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。

这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心3.2中的问题。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了3.1中的低效问题。

最后 instance 是在第一次加载 SingletonContainer 类时被创建的,而 SingletonContainer 类则在调用 getInstance 方法的时候才会被加载,因此也实现了惰性加载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/557332.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

递推算法之滚动数组思维方式

概述 在算法的最终结果只用到本层与上一层的结果时, 可以使用滚动数组思想。 简单的理解就是每次都使用固定的几个存储空间达到压缩节省存储空间的作用, 主要用在递推算法中。示例1: 爬楼梯问题 假设你正在爬楼梯。需要 n 阶你才能到达楼顶…

Java创建并执行线程的四种方法

Java创建并执行线程的四种方法 java里面创建线程有四种方式: 无返回: 实现Runnable接口,重写run();继承Thread类,重写run(); 有返回:实现Callable接口,重写call(),利用FutureTask包装Callable&#xff0c…

idea中svn的更新、检出、提交操作

一、首先集成svn到idea 点击号连接svn仓库地址 等待代码下载完毕后就可以对代码进行update,commit操作了 更新操作方法一:项目上右键 方法二:点击快捷图标 方法三: 代码提交 方法一 方法二: 方法三: 会跳出窗口: 然后点击Commit 如果检测代码有错误会询问你是否要处理,一般确定…

判断链表是否相交并找出交点

问题概述 单链表定义如下: public class ListNode {int val;ListNode next;ListNode(int x) {val x;next null;}}编写程序, 找出两个链表的交点。 如图所示,链表 A 和链表 B 在节点 8 处相交。 算法思路 首先确定一个事情: …

socket和http区别有哪些

socket和http区别有哪些 1、socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉; 2、http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉。 socket和http区别&…

Spring Bean 的生命周期

概述 Spring 启动,扫描指定的包路径,查找需要被 Spring 管理的 bean构造 BeanDefinition 对象实例化 bean,如果有多个构造方法,则需要推断构造方法,确定好构造方法后,进行实例化得到一个对象进行 bean 的属…

兄弟3150cdn更换硒鼓_耗材知多点:一体式硒鼓及分离式硒鼓

相信第一次接触硒鼓的小伙伴们,会比较诧异为什么有些硒鼓可以直接装机使用,而有些硒鼓,却需要两个部件组合起来或分别装机才能正常使用。今天就带大家来了解一下什么是一体式硒鼓,什么又是分离式硒鼓。①一体式硒鼓:以…

Java IO流之PrintStream分析

简介 PrintStream继承了FilterOutputStream.是"装饰类"的一种,所以属于字节流体系中(与PrintStream相似的流PrintWriter继承于Writer,属于字符流体系中),为其他的输出流添加功能.使它们能够方便打印各种数据值的表示形式.此外,值得注意的是: 与其他流不同的是,Prin…

bs4爬取的时候有两个标签相同_10分钟用Python爬取最近很火的复联4影评

《复仇者联盟4:终局之战》已经上映快三个星期了,全球票房破24亿美元,国内票房破40亿人民币。虽然现在热度逐渐下降,但是我们还是恬不知耻地来蹭一蹭热度。上映伊始《复联4》的豆瓣评分曾破了9分。后来持续走低,现在《复…

RabbitMQ 基本概念与高级特性

文章目录1. 什么是消息队列1.1 消息队列概述1.2 使用消息队列的优势1.3 使用消息队列的劣势1.4 常见的消息队列产品对比2. RabbitMQ 基本概念2.1 RabbitMQ 概述2.2 RabbitMQ 的概念模型2.2.1 Message2.2.2 Publisher2.2.3 Exchange2.2.4 Binding2.2.5 Queue2.2.6 Connection2.2…

HTTP 和 SOCKET 的区别

HTTP 和 SOCKET 的区别 要弄明白 http 和 socket 首先要熟悉网络七层:物 数 网 传 会 表 应,如图1 如图1 HTTP 协议:超文本传输协议,对应于应用层,用于如何封装数据. TCP/UDP 协议:传输控制协议,对应于传输层&…

java 8进制串转中文_为什么不能用中文进行编程?而英文就可以

前些天大雄无意间听见几个线下班小伙伴说真的是无(te)意(di)的“我要补英文”“对,英文真的很重要”“如果编码用中文就好了”...听见这大雄就不淡定了中文代码小伙伴确定能够搞懂??首先我们大概的看一下中文编码:你以为会写中文写…

Java 父子类方法调用顺序

概述 在 Java 的多态中,有个结论可以直接引用: 对象类型看左边静态方法和成员变量看左边成员方法,编译看左边,运行看右边(子类可能重写)如果有多个匹配的方法可以调用,优先调用参数最匹配的一个 调用顺序…

TCP/IP,HTTP,Socket的区别与联系

TCP/IP,HTTP,Socket的区别与联系 网络七层:物数网传会表应.分别为物理层,数据链路层,网络层,传输层,会话层,表示层,应用层.其中,底层三层:物理层,数据链路层,网络层是网络工程师研究的对象,而其它四层,是用户面向和关心的问题. http协议:超文本传输协议, 对应于应用层. tcp协议…

MATLAB学习笔记(一)求解三阶微分方程

一、求解三阶微分方程 对于多变量三阶微分方程求解问题,这里介绍一种求解方法。 例题如下: 对于以上方程,给定边界条件,,,,,。求解和的表达式。 二、解题步骤 (1&…

Docker exec 命令执行出错, 显示 the input device is not aTTY 的解决办法

问题描述 在使用 docker exec 命令进入容器时,发现报错信息如下: the input device is not a TTY. If you are using mintty, try prefixing the command with winpty解决办法 这是因为命令行权限不足导致的,解决办法就是提升权限 Windo…

JAVA 判断Socket 远程端是否断开连接

JAVA 判断Socket 远程端是否断开连接 最近在做项目的时候,遇到这样一个问题,如何判断 Socket 远程端连接是否关闭,如果关闭的话,就要重建连接Socket的类提供了一些已经封装好的方法, 如 isClosed()、isConnected()、i…

axure 内部框架内容下滑_Axure教程:转盘抽奖交互原型

本文跟大家分享,如何使用axure制作转盘抽奖交互原型,不带登录流程。效果如下:抽奖流程一、主要内容(1)主要元件:动态面板(2)重点:旋转交互、随机函数、触发动作。(3)难点:通过停止位置判断抽奖结果(4)涉及函…

java.lang.relect.Array 类

概述 这是一个位于 java.lang.reflect 包下的类,类中的方法都是静态方法,主要的功能就是更方便地创建数组。在数组元素类型未知时,简化了操作数组的代码。 使用示例 //创建一个长度为 5 的整形数组 //等价于 int[] array new int[4]; int…

日志打印的8种级别(很详细)

日志打印的8种级别(很详细) 日志的输出都是分级别的,不同的设置不同的场合打印不同的日志。下面拿最普遍用的Log4j日志框架来做个日志级别的说明,其他大同小异。 Log4j的级别类org.apache.log4j.Level里面定义了日志级别&#x…