lazy-init 懒加载的艺术
懒加载是一种加载方式,加载单例对象一般有两种方式,一是在启动时就立即创建好,另一种则是在需要用到的时候再去加载即懒加载。懒加载一般会针对单例场景,且一般是针对在加载消耗较大费时,且不一定会用到的场景。
好了,相信啥意思大家都明白!那么具体如何实现呢?其实挺有意思的!
方案1. 直接用懒加载实例进行判断null,不安全的做法
public class UnsafeLazyInit{
private static Instance instance; public static Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}
很明显,这里的懒加载是不是安全的,因为当线程并发访问 getInstance(), 可能同时看到为null,从而同时进行了初始化,这会导致外部获取到的实例是不致的,从而导致不可预估的错误!另一种隐形的加载错误待说!
方案2. 使用锁将懒加载方法锁起来,不省力的做法
如上,既然既然访问 getInstance() 是非线程安全的,那么,只要加个锁就可以了!
public static syncronized Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
很明显,这种做法在高并发的情况下,会有严重的锁竞争,从而导致严重的性能问题!相信不会有同学这么干!
方案3. 双重锁校验的普通方法,一个有隐患的做法
如上两个问题,既要线程安全,又要性能不影响,其实可以想像到,初始化动作只是一次性的,所以,只要第一次的时候保证线程安全即可,因为后续大家都是获取同一个实例!所以,我们把锁的位置放到第一次加载时!
public static Instance getInstance(){
if (instance == null){
// 只有在instance为null即未进行过初始化时,才会上锁,从而避免后续性能问题
synchronized(UnsafeLazyInit.class){
// 由于外面的判定是非线程安全的,上锁后,再次进行判定是否已创建
if (instance == null){
// 此处初始化可能出现重排序
instance = new Instance();
}
}
}
return instance;
}
所以,如上的解决办法,看起来很完美。但其实还是有问题的!问题在于,外部的 instance == null 初始是非线程安全的,任何进入的线程都可以进行断定!
而 instance = new Instance(); 语句,并不是像代码看起来那样,就一句,可以保证原子性!
这一条语句在实际执行中,可能会被拆分程三条语句,即分配内存/初始化类变量/赋值给实例变量,大致如下:
memory = allocate();
ctorInstance(memory);
instance = memory;
由于jvm的jit编译优化,可能会重排序,在保证结果最终一致的前提下,会将分配内存和赋值实例变量做不确定的重排,而当发生重排后,即先赋值实例变量内存空间,那么由于外部非线程安全的获取实例变量,会立即读取到该变量不为null,从而得到一个未初始化完成的实例进行后续操作!这将带来不可预知的后果!所以,这种双重锁是有问题的!不过看起来问题范围已经很小了!
方案4. 双重锁校验的增强方法,完美
综上双重锁方法,还存在一个重排序问题,一般针对重排序,我们要条件反射式的想到禁止重排序即可。而jvm禁止重排的方式,有 volatile, final, 等关键词,当然其实现原理都是加入一些内存屏障来保障不重排。不管怎么样,我们只需要使用一些关键词就可以了!
private volatile static Instance instance;
public static Instance getInstance(){
if (instance == null){
// 只有在instance为null即未进行过初始化时,才会上锁,从而避免后续性能问题
synchronized(UnsafeLazyInit.class){
// 由于外面的判定是非线程安全的,上锁后,再次进行判定是否已创建
if (instance == null){
// 使用volatile后,禁止了jit重排优化
instance = new Instance();
}
}
}
return instance;
}
方案5. 使用类初始化机制创建对象,一种脆弱的加载方式
这是一种基于类初始化锁的一种懒加载方式!将懒加载放在一个类的静态变量上,依赖于类的安全的类加载来保证期实例化的线程安全性和准确性!如下:
public class InstanceFactory {
private static class InstanceHolder {
// 懒加载实例化放到内部类的静态变量上,需确定两个问题,1. 初始化时机,2. 线程安全性
public static Instance instance = new Instance();
} public static Instance getInstance() {
return InstanceHolder.instance;
}
}
这看起来虽然有点麻烦,但是理解起来不会有问题!但是有问题我们得考虑下:静态变量不是一开始就会加载出来吗?如果这样的话,就不存在懒加载了啊!
其实static静态变量是在类初始化的时候才会操作的。
而类的初始化则有几个时机:
1. 类首次被创建实例,即 new xxx() 操作时,触发类初始化;
2. 类中的静态方法被首次调用,比如上面的 getInstance() 被首次调用时会触发当前类的初始化;
3. 类的静态字段被赋值,比如 A.instance = abc;
4. 类中的一个非常量字段被使用,常量则不会触发初始化;
5. 类一个顶级类,而且一个断言语句嵌套在类内部执行;(我也不太明白啥意思)
来看一下实际的例子,说明类的初始化时机:
@Test
public void testClassInit() {
// new 就不多说了
// 静态方法被使用
A.getInstance();
// 常量使用不会触发类初始化
System.out.println(A.noneInitConst);
// 静态变量被赋值触发类初始化
A.setVarInit = "";
System.out.println("setVarInit=''");
// 非常量静态变量被使用触发类初始化
System.out.println(A.usedVarInit);
}
static class A {
// 类常量被使用,不会触发类初始化
public static final String noneInitConst = "a";
// 静态变量赋值
static String setVarInit;
// 静态变量使用,触发类初始化
static String usedVarInit = "c";
static {
// 类初始化时会执行该静态块
System.out.println("A executed...");
}
// 静态方法触发类初始化
public static String getInstance() {
// 为避免其他规则被触发,直接使用返回字符串
return "A";
}
}
可以依次注释各规则,查看初始化效果!
ok,明白了类初始化的时机后,我们知道了,这里的懒加载是有用的!
但是还有个问题,就是类初始化难道不会并发吗?答案是一定的,既然执行时机一致,那么并发自然存在。
类初始化时,jvm会去获取一个锁,从而保证同步多个线程对同一个类的初始化!这个从jdk的ClassLoader实现可以看出来!
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 获取锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
所以,类的初始化是线程安全的!从而得出,使用类的 static 变量进行懒加载的正确性!
当然,这里无故又多了一个内部类,着实让人不爽,而且如果后面往这个类里加入其他变量,则可能一不小心导致了问题!
不管怎么样,他能解决当下的问题!
方案6. 使用第三方变量来标识懒加载情况
意思是说,这里的初始化,是一个大对象的初始化,那么我可以换一个 true | false, 的简单变量的判定来处理,变量虽简单,不过也可能遇到的问题其实和上面一样,就不赘述了! 具体做法就是,在完成加载之后,再将该第变量值改变即可!
但是使用第三方变量还有个好处,就是可以很方便的执行代码块!复杂的加载逻辑,你懂的!
综上,懒加载这个简单操作,还真是充满了艺术感呢!
参考: 《java并发编程的艺术》
lazy-init 懒加载的艺术的更多相关文章
- lazy图片懒加载使用
看到一个小伙子写的言简意赅很不错,摘录如下: https://www.npmjs.com/package/vue-lazyload 首先我们先在npm上下载vue-lazyload的包 1 npm i ...
- Django lazy load 懒加载 倒序查询
Django orm默认懒加载 Django orm默认使用的懒加载,即使用的时候才去访问数据库,且每次默认取最少的数据,当然这样有好处也有坏处... 坏处: 会导致频繁的查询数据库,如涉及到外键 ...
- 插件:★★★ !!!图片懒加载 lazyload.js 、 jquery.scrollLoading.js
插件:图片懒加载 jquery.lazyload.js 2016-3-31 插件说明:http://www.w3cways.com/1765.html (小插件,好用) 下载地址: https://r ...
- Spring5.0源码学习系列之浅谈懒加载机制原理
前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文挑一个比较重要的知识点Bean的懒加载进行学习 1.什么是懒加载? 懒加载(Lazy-ini ...
- 解决hibernate中的懒加载(延迟加载)问题
解决hibernate中的懒加载(延迟加载)问题 我们在开发的时候经常会遇到延迟加载问题,在实体映射时,多对一和多对多中,多的一样的属性默认是lazy="true"(即,默认是 ...
- iOS 开发UI篇 -- 懒加载学习
1. 懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载( 效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了,如果没有那么再去进行实 ...
- Hibernate 性能优化之懒加载
针对数据库中的大数据,不希望特别早的加载到内存中,当用到它的时候才加载 懒加载分为:类的懒加载.集合的懒加载.单端关联的懒加载 类的懒加载 1.在默认情况下,类就是执行懒加载 2. ...
- @Basic表示一个简单的属性 懒加载,急加载
5.@Basic(fetch=FetchType,optional=true) 可选 @Basic表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的getXxxx()方法,默认 即为 @Ba ...
- mybatis中的懒加载
知识点:mybatis中的懒加载的使用 参考:https://www.cnblogs.com/ysocean/p/7336945.html?utm_source=debugrun&utm_me ...
随机推荐
- python对mysql数据库的一些常用操作
import pymysql class OperationDatabase(): def __init__(self,Ip,User,PassWd,DBname): self.ip=Ip self. ...
- vue中提示$index is not defined
今天学习Vue中遇到了一个报错信息:$index is not defined,是我写了个for循环在HTML中,然后是因为版本的问题 下面是解决方法: 原来的是 v-for="person ...
- 【转】异步编程 In .NET
概述 在之前写的一篇关于async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试.今天我们再来回答一下这个问题,同时我们 ...
- 《Miracle_House》团队项目系统设计改进
一.团队项目系统设计改进: 1.分析项目系统设计说明书初稿的不足,特别是软件系统结构模型建模不完善内容: 对于原文档中,设计图中存在的错误以及文字描述不准确的地方进行了修改. 2. 团队项目Githu ...
- Startls Back 引起的 win10升级之后的闪屏问题
win10 更新之后出现闪频问题. 有人说是和startls back 有关,需要卸载startls back, 但是进入安全模式下显示此 程序无法打开,无法卸载. 后来看到有人更新到startls ...
- py3.0第四天 函数,生成器迭代器等
1.列表生成式,迭代器&生成器 孩子,我现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],我要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式 > ...
- UTF-8的BOM含义
BOM的介绍 在github上写md文件的时候,发现生成自己blog时,报出一个错误是让使用UTF-8编码,然后在Notepad++上把文件转成UTF-8时,发现菜单中有"UTF-8无BOM ...
- 关于chrome密码保存框的神坑,这样子解决就行
- temp--内蒙农信出差
============================2018.09.18~~~20181001================================== -------住宿----黎明花 ...
- lombok学习
lombok的官方地址:https://projectlombok.org/ lombok的Github地址:https://github.com/rzwitserloot/lombok lombok ...