1.学习编程|Spring源码深度解析 读书笔记 第4章:bean的码解加载
2.画图带你彻底弄懂三级缓存和循环依赖的问题
3.Go并åç¼ç¨ â sync.Once
学习编程|Spring源码深度解析 读书笔记 第4章:bean的加载
在Spring框架中,bean的码解加载过程是一个精细且有序的过程。首先,码解当需要加载bean时,码解Spring会尝试通过转换beanName来识别目标对象,码解可能涉及到别名或FactoryBean的码解66商业源码识别。
加载过程分为几步:从缓存查找单例,码解Spring容器内单例只创建一次,码解若缓存中无数据,码解会尝试从singletonFactories寻找。码解接着是码解bean的实例化,从缓存获取原始状态后,码解可能需要进一步处理以符合预期状态。码解
原型模式的码解依赖检查是单例模式特有的,用来避免循环依赖问题。码解然后,如果缓存中无数据,会检查parentBeanFactory,递归加载配置。BeanDefinition会被转换为RootBeanDefinition,合并父类属性,确保依赖的正确初始化。
Spring根据不同的scope策略创建bean,如singleton、prototype等。类型转换是后续步骤,可能将返回的bean转换为所需的类型。FactoryBean的使用提供了灵活的实例化逻辑,用户自定义创建bean的过程。
当bean为FactoryBean时,getBean()方法代理了FactoryBean的getObject(),允许通过不同的方式配置bean。缓存中获取单例时,会执行循环依赖检测和性能优化。最后,通过ObjectFactory实例singletonFactory定义bean的完整加载逻辑,包括回调方法用于处理单例创建前后的帝国时代源码状态。
画图带你彻底弄懂三级缓存和循环依赖的问题
大家好。我们都知道,Spring可以通过三级缓存解决循环依赖的问题,这也是面试中很常见的一个面试题,本文就来着重讨论一下有关循环依赖和三级缓存的问题。一、什么是循环依赖大家平时在写业务的时候应该写过这样的代码。
其实这种类型就是循环依赖,就是AService 和BService两个类相互引用。
二、三级缓存可以解决的循环依赖场景如上面所说,大家平时在写这种代码的时候,项目其实是可以起来的,也就是说其实三级缓存是可以解决这种循环依赖的。
当然除了这种字段注入,set注入也是可以解决的,代码如下。
接下来就来探究三级缓存是如何解决这种循环依赖的?
三、Spring的Bean是如何创建出来的本文所说的Bean和对象可以理解为同一个意思。
先说如何解决循环依赖之前,先来了解一下一个Bean创建的大致流程。为什么要说Bean的创建过程,因为循环依赖主要是发生在Bean创建的过程中,知道Bean是如何创建的,才能更好的理解三级缓存的作用。
其实Spring Bean的生命周期源码剖析我也在微信公众号 三友的java日记 中发过,并且有简单的提到三级缓存,有兴趣的同学可以在关注公众号之后回复 Bean 即可获取文章链接,里面有Bean创建过程更详细的说明。这里我简单画一张图来说一下。
其实图里的每个阶段还可以分为一些小的阶段,我这里就没画出来了。
来说一下每个阶段干了什么事。
BeanDefinition的读取阶段:我们在往Spring容器注入Bean的时候,一般会通过比如xml方式,@Bean注解的jdk 源码注解汉语方式,@Component注解的方式,其实不论哪一种,容器启动的时候都会去解析这些配置,然后为每个Bean生成一个对应的BeanDefinition,这个BeanDefinition包含了这个Bean的创建的信息,Spring就是根据BeanDefinition去决定如何创建一个符合你要求的Bean
Bean的实例化阶段:这个阶段主要是将你配置的Bean根据Class的类型创建一个对象出来
Bean的属性赋值阶段:这个阶段主要是用来处理属性的赋值,比如@Autowired注解的生效就是在这个阶段的
Bean的初始化阶段:这个阶段主要是回调一些方法,比如你的类实现了InitializingBean接口,那么就会回调afterPropertiesSet方法,同时动态代理其实也是在这个阶段完成的。
其实从这可以看出,一个Spring Bean的生成要分为很多的阶段,只有这些事都处理完了,这个Bean才是完完全全创建好的Bean,也就是我们可以使用的Bean。
四、三级缓存指的是哪三级缓存这里直接上源码
第一级缓存:singletonObjects
存放已经完完全全创建好的Bean,什么叫完完全全创建好的?就是上面说的是,所有的步骤都处理完了,就是创建好的Bean。一个Bean在产的过程中是需要经历很多的步骤,在这些步骤中可能要处理@Autowired注解,又或是处理@Transcational注解,当需要处理的都处理完之后的Bean,就是完完全全创建好的Bean,这个Bean是可以用来使用的,我们平时在用的Bean其实就是创建好的。
第二级缓存:earlySingletonObjects
早期暴露出去的Bean,其实也就是解决循环依赖的Bean。早期的意思就是没有完完全全创建好,但是由于有循环依赖,就需要把这种Bean提前暴露出去。其实 早期暴露出去的Bean 跟 完完全全创建好的Bean 他们是同一个对象,只不过早期Bean里面的注解可能还没处理,完完全全的Bean已经处理了完了,但是宝宝个人网站源码他们指的还是同一个对象,只不过它们是在Bean创建过程中处于的不同状态,如果早期暴露出去的Bean跟完完全全创建好的Bean不是同一个对象是会报错的,项目也就起不来,这个不一样导致报错问题,这里我会结合一个案例再来写一篇文章,这里不用太care,就认为是一样的。
第三级缓存:singletonFactories
存的是每个Bean对应的ObjectFactory对象,通过调用这个对象的getObject方法,就可以获取到早期暴露出去的Bean。
注意:这里有个很重要的细节就是三级缓存只会对单例的Bean生效,像多例的是无法利用到三级缓存的,通过三级缓存所在的类名DefaultSingletonBeanRegistry就可以看出,仅仅是对SingletonBean也就是单例Bean有效果。
五、三级缓存在Bean生成的过程中是如何解决循环依赖的这里我假设项目启动时先创建了AService的Bean,那么就会根据Spring Bean创建的过程来创建。
在Bean的实例化阶段,就会创建出AService的对象,此时里面的@Autowired注解是没有处理的,创建出AService的对象之后就会构建AService对应的一个ObjectFactory对象放到三级缓存中,通过这个ObjectFactory对象可以获取到AService的早期Bean。
然后AService继续往下走,到了某一个阶段,开始处理@Autowired注解,要注入BService对象,如图
要注入BService对象,肯定要去找BService对象,那么他就会从三级缓存中的第一级缓存开始依次查找有没有BService对应的Bean,肯定都没有啊,因为BService还没创建呢。没有该怎么办呢?其实很好办,没有就去创建一个么,这样不就有了么。于是AService的注入BService的过程就算暂停了,因为现在得去创建BService,java字符动画源码创建之后才能注入给AService。
于是乎,BService就开始创建了,当然他也是Spring的Bean,所以也按照Bean的创建方式来创建,先实例化一个BService对象,然后缓存对应的一个ObjectFactory到第三级缓存中,然后就到了需要处理@Autowired注解的时候了,如图。
@Autowired注解需要注入AService对象。注入AService对象,就需要先去拿到AService对象,此时也会一次从三级缓存查有没有AService。
先从第一级查,有没有创建好的AService,肯定没有,因为AService此时正在在创建(因为AService在创建的过程中需要注入BService才去创建BService的,虽然此刻代码正在创建BService,但是AService也是在创建的过程中,只不过暂停了,只要BService创建完,AService会继续往下创建);第一级缓存没有,那么就去第二级看看,也没有,没有早期的AService;然后去第三级缓存看看有没有AService对应的ObjectFactory对象,惊天的发现,竟然有(上面提到过,创建出AService的对象之后,会构建AService对应的一个ObjectFactory对象放到三级缓存中),那么此时就会调用AService对应的ObjectFactory对象的getObject方法,拿到早期的AService对象,然后将早期的AService对象放到二级缓存,为什么需要放到二级缓存,主要是怕还有其他的循环依赖,如果还有的话,直接从二级缓存中就能拿到早期的AService对象。
虽然是早期的AService对象,但是我前面说过,仅仅只是早期的AService对象可能有些Bean创建的步骤还没完成,跟最后完完全全创建好的AService Bean是同一个对象。
于是接下来就把早期的AService对象注入给BService。
此时BService的@Autowired注解注入AService对象就完成了,之后再经过其他阶段的处理之后,BService对象就完完全全的创建完了。
BService对象创建完之后,就会将BService放入第一级缓存,然后清空BService对应的第三级缓存,当然也会去清空第二级缓存,只是没有而已,至于为什么清空,很简单,因为BService已经完全创建好了,如果需要BService那就在第一级缓存中就能查找到,不需要在从第二级或者第三级缓存中找到早期的BService对象。
BService对象就完完全全的创建完之后,那么接下来该干什么呢?此时当然继续创建AService对象了,你不要忘了为什么需要创建BService对象,因为AService对象需要注入一个BService对象,所以才去创建BService的,那么此时既然BService已经创建完了,那么是不是就应该注入给AService对象了?所以就会将BService注入给AService对象,这下就明白了,BService在构建的时候,已经注入了AService,虽然是早期的AService,但的确是AService对象,现在又把BService注入给了AService,那么是不是已经解决了循环依赖的问题了,AService和BService都各自注入了对方,如图。
然后AService就会跟BService一样,继续处理其它阶段的,完全创建好之后,也会清空二三级缓存,放入第一级缓存。
到这里,AService和BService就都创建好了,循环依赖也就解决了。
这下你应该明白了三级缓存的作用,主要是第二级和第三级用来存早期的对象,这样在有循环依赖的对象,就可以注入另一个对象的早期状态,从而达到解决循环依赖的问题,而早期状态的对象,在构建完成之后,也就会成为完完全全可用的对象。
六、三级缓存无法解决的循环依赖场景1)构造器注入无法解决循环依赖上面的例子是通过@Autowired注解直接注入依赖的对象,但是如果通过构造器注入循环依赖的对象,是无法解决的,如代码下
构造器注入就是指创建AService对象的时候,就传入BService对象,而不是用@Autowired注解注入BService对象。
运行结果
启动时就会报错,所以通过构造器注入对象就能避免产生循环依赖的问题,因为如果有循环依赖的话,那么就会报错。
至于三级缓存为什么不能解决构造器注入的问题呢?其实很好理解,因为上面说三级缓存解决循环依赖的时候主要讲到,在AService实例化之后,会创建对应的ObjectFactory放到第三级缓存,发生循环依赖的时候,可以通过ObjectFactory拿到早期的AService对象;而构造器注入,是发生在实例化的时候,此时还没有AService对象正在创建,还没完成,压根就还没执行到往第三级添加对应的ObjectFactory的步骤,那么BService在创建的时候,就无法通过三级缓存拿到早期的AService对象,拿不到怎么办,那就去创建AService对象,但是AService不是正在创建么,于是会报错。
2)注入多例的对象无法解决循环依赖**启动引导类
要获取AService对象,因为多例的Bean在容器启动的时候是不会去创建的,所以得去获取,这样就会创建了。
运行结果
为什么不能解决,上面在说三级缓存的时候已经说过了,三级缓存只能对单例Bean生效,那么多例是不会起作用的,并且在创建Bean的时候有这么一个判断,那就是如果出现循环依赖并且是依赖的是多例的Bean,那么直接抛异常,源码如下
注释其实说的很明白,推测出现了循环依赖,抛异常。
所以上面提到的两种循环依赖的场景,之所以无法通过三级缓存来解决,是因为压根这两种场景就无法使用三级缓存,所以三级缓存肯定解决不掉。\
七、不用三级缓存,用二级缓存能不能解决循环依赖遇到这种面试题,你就跟面试官说,如果行的话,Spring的作者为什么不这么写呢?
哈哈,开个玩笑,接下来说说到底为什么不行。
这里我先说一下前面没提到的细节,那就是通过ObjectFactory获取的Bean可能是两种类型,第一种就是实例化阶段创建出来的对象,还是一种就是实例化阶段创建出来的对象的代理对象。至于是不是代理对象,取决于你的配置,如果添加了事务注解又或是自定义aop切面,那就需要代理。这里你不用担心,如果这里获取的是代理对象,那么最后完全创建好的对象也是代理对象,ObjectFactory获取的对象和最终完全创建好的还是同一个,不是同一个肯定会报错,所以上面的理论依然符合,这里只是更加的细节化。
有了这个知识点之后,我们就来谈一下为什么要三级缓存。
第一级缓存,也就是缓存完全创建好的Bean的缓存,这个缓存肯定是需要的,因为单例的Bean只能创建一次,那么肯定需要第一级缓存存储这些对象,如果有需要,直接从第一级缓存返回。那么如果只能有二级缓存的话,就只能舍弃第二级或者第三级缓存。
假设舍弃第三级缓存
舍弃第三级缓存,也就是没有ObjectFactory,那么就需要往第二缓存放入早期的Bean,那么是放没有代理的Bean还是被代理的Bean呢?
1)如果直接往二级缓存添加没有被代理的Bean,那么可能注入给其它对象的Bean跟最后最后完全生成的Bean是不一样的,因为最后生成的是代理对象,这肯定是不允许的;
2)那么如果直接往二级缓存添加一个代理Bean呢?
假设没有循环依赖,提前暴露了代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错,
假设没有循环依赖,使用了ObjectFactory,那么就不会提前暴露了代理对象,到最后生成的对象是什么就是什么,就不会报错,
如果有循环依赖,不论怎样都会提前暴露代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错
通过上面分析,如果没有循环依赖,使用ObjectFactory,就减少了提前暴露代理对象的可能性,从而减少报错的可能。
假设舍弃第二级缓存
假设舍弃第二级缓存,也就是没有存放早期的Bean的缓存,其实肯定也不行。上面说过,ObjectFactory其实获取的对象可能是代理的对象,那么如果每次都通过ObjectFactory获取代理对象,那么每次都重新创建一个代理对象,这肯定也是不允许的。
从上面分析,知道为什么不能使用二级缓存了吧,第三级缓存就是为了避免过早地创建代理对象,从而避免没有循环依赖过早暴露代理对象产生的问题,而第二级缓存就是防止多次创建代理对象,导致对象不同。
本文完。
如果觉得这篇文章对你有所帮助,还请帮忙点赞、在看、转发给更多的人,码字不易,非常感谢!
欢迎关注公众号 三友的java日记,更多技术干货及时获得。原文:/post/Go并åç¼ç¨ â sync.Once
ç®ä»
Once å¯ä»¥ç¨æ¥æ§è¡æ个å½æ°ï¼ä½æ¯è¿ä¸ªå½æ°ä» ä» åªä¼æ§è¡ä¸æ¬¡ï¼å¸¸å¸¸ç¨äºåä¾å¯¹è±¡çåå§ååºæ¯ã说å°è¿ï¼å°±ä¸å¾ä¸è¯´ä¸ä¸åä¾æ¨¡å¼äºã
åä¾æ¨¡å¼åä¾æ¨¡å¼æææ±å¼å饿æ±å¼ä¸¤ç§ï¼ä¸ä»£ç ã
饿æ±å¼é¥¿æ±å¼é¡¾åæä¹å°±æ¯æ¯è¾é¥¥é¥¿ï¼æ以就æ¯ä¸æ¥å°±åå§åã
var?instance?=?&Singleton{ }type?Singleton?struct?{ }func?GetInstance()?*Singleton?{ return?instance}ææ±å¼ææ±å¼é¡¾åæä¹å°±æ¯å·æï¼å¨è·åå®ä¾çæ¶åå¨è¿è¡åå§åï¼ä½æ¯ææ±å¼ä¼æ并åé®é¢ã并åé®é¢ä¸»è¦åçå¨ instance == nil è¿ä¸ªå¤ææ¡ä»¶ä¸ï¼æå¯è½å¤ä¸ª goruntine åæ¶è·å instance 对象é½æ¯ nil ï¼ç¶åé½å¼å§åå»ºäº Singleton å®ä¾ï¼å°±ä¸æ»¡è¶³åä¾æ¨¡å¼äºã
var?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}å éæ们é½ç¥é并åé®é¢åºç°åï¼å¯ä»¥éè¿å éæ¥è¿è¡è§£å³ï¼å¯ä»¥ä½¿ç¨ sync.Metux æ¥å¯¹æ´ä¸ªæ¹æ³è¿è¡å éï¼å°±ä¾å¦ä¸é¢è¿æ ·ãè¿ç§æ¹å¼æ¯è§£å³äºå¹¶åçé®é¢ï¼ä½æ¯éçç²åº¦æ¯è¾é«ï¼æ¯æ¬¡è°ç¨ GetInstance æ¹æ³çæ¶åé½éè¦è·å¾éæè½è·å¾ instance å®ä¾ï¼å¦æå¨è°ç¨é¢çæ¯è¾é«çåºæ¯ä¸æ§è½å°±ä¸ä¼å¾å¥½ãé£æä»ä¹æ¹å¼å¯ä»¥è§£å³åï¼è®©æ们æ¥çå¾ä¸çå§
var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ mutex.Lock()defer?mutex.Unlock()if?instance?==?nil?{ ?instance?=?&Singleton{ }}return?instance}Double Check为äºè§£å³éçç²åº¦é®é¢ï¼æ们å¯ä»¥ä½¿ç¨ Double Check çæ¹å¼æ¥è¿è¡è§£å³ï¼ä¾å¦ä¸é¢ç代ç ï¼ç¬¬ä¸æ¬¡å¤æ instance == nil ä¹åéè¦è¿è¡å éæä½ï¼ç¶åå第äºæ¬¡å¤æ instance == nil ä¹åæè½å建å®ä¾ãè¿ç§æ¹å¼å¯¹æ¯ä¸é¢çæ¡ä¾æ¥è¯´ï¼éçç²åº¦æ´ä½ï¼å 为å¦æ instance != nil çæ åµä¸æ¯ä¸éè¦å éçãä½æ¯è¿ç§æ¹å¼å®ç°èµ·æ¥æ¯ä¸æ¯æ¯è¾éº»ç¦ï¼æ没æä»ä¹æ¹å¼å¯ä»¥è§£å³å¢ï¼
var?mutex?sync.Mutexvar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ if?instance?==?nil?{ ?mutex.Lock()?defer?mutex.Unlock()?if?instance?==?nil?{ ?instance?=?&Singleton{ }?}}return?instance}ä½¿ç¨ sync.Onceå¯ä»¥ä½¿ç¨ sync.Once æ¥å®ç°åä¾çåå§åé»è¾ï¼å 为è¿ä¸ªé»è¾è³å¤åªä¼è·ä¸æ¬¡ãæ¨è使ç¨è¿ç§æ¹å¼æ¥è¿è¡åä¾çåå§åï¼å½ç¶ä¹å¯ä»¥ä½¿ç¨é¥¿æ±å¼ã
var?once?sync.Oncevar?instance?*Singletontype?Singleton?struct?{ }func?GetInstance()?*Singleton?{ once.Do(func()?{ ?instance?=?&Singleton{ }})return?instance}æºç åæä¸é¢å°±æ¯ sync.Once å çæºç ï¼æå é¤äºæ³¨éï¼ä»£ç ä¸å¤ï¼Once æ°æ®ç»æ主è¦ç± done å m ç»æï¼å ¶ä¸ done æ¯åå¨ f å½æ°æ¯å¦å·²æ§è¡ï¼m æ¯ä¸ä¸ªéå®ä¾ã
type?Once?struct?{ done?uint?//?få½æ°æ¯å¦å·²æ§è¡mMutex?//?é}func?(o?*Once)?Do(f?func())?{ if?atomic.LoadUint(&o.done)?==?0?{ ?o.doSlow(f)}}func?(o?*Once)?doSlow(f?func())?{ o.m.Lock()defer?o.m.Unlock()if?o.done?==?0?{ ?defer?atomic.StoreUint(&o.done,?1)?f()}}Do æ¹æ³
ä¼ å ¥ä¸ä¸ª functionï¼ç¶å sync.Once æ¥ä¿è¯åªæ§è¡ä¸æ¬¡ï¼å¨ Do æ¹æ³ä¸ä½¿ç¨ atomic æ¥è¯»å done åéï¼å¦ææ¯ 0 ï¼å°±ä»£ç f å½æ°æ²¡æ被æ§è¡è¿ï¼ç¶åå°±è°ç¨ doSlowæ¹æ³ï¼ä¼ å ¥ f å½æ°
doShow æ¹æ³
doShow ç第ä¸ä¸ªæ¥éª¤å°±æ¯å å éï¼è¿éå éçç®çæ¯ä¿è¯åä¸æ¶å»æ¯è½ç±ä¸ä¸ª goruntine æ¥æ§è¡ doSlow æ¹æ³ï¼ç¶åå次å¤æ done æ¯å¦æ¯ 0 ï¼è¿ä¸ªå¤æå°±ç¸å½äºæ们ä¸é¢è¯´ç DoubleCheck ï¼å 为 doSlow å¯è½åå¨å¹¶åé®é¢ãç¶åæ§è¡ f æ¹æ³ï¼ç¶åæ§è¡ä½¿ç¨ atomic å° done ä¿åæ 1ãä½¿ç¨ DoubleCheck ä¿è¯äº f æ¹æ³åªä¼è¢«æ§è¡ä¸æ¬¡ã
æ¥ççï¼é£å¯ä»¥è¿æ ·å®ç° sync.Once åï¼
è¿æ ·ä¸æ¯æ´ç®åä¸ç¹åï¼ä½¿ç¨ååç CAS æä½å°±å¯ä»¥è§£å³å¹¶åé®é¢åï¼å¹¶ååªæ§è¡ä¸æ¬¡ f æ¹æ³çé®é¢æ¯å¯ä»¥è§£å³ï¼ä½æ¯ Do æ¹æ³å¯è½å¹¶åï¼ç¬¬ä¸ä¸ªè°ç¨è å° done 设置æäº 1 ç¶åè°ç¨ f æ¹æ³ï¼å¦æ f æ¹æ³ç¹å«èæ¶é´ï¼é£ä¹ç¬¬äºä¸ªè°ç¨è è·åå° done 为 1 å°±ç´æ¥è¿åäºï¼æ¤æ¶ fæ¹æ³æ¯æ²¡ææ§è¡è¿ç¬¬äºæ¬¡ï¼ä½æ¯æ¤æ¶ç¬¬äºä¸ªè°ç¨è å¯ä»¥ç»§ç»æ§è¡åé¢ç代ç ï¼å¦æåé¢ç代ç ä¸æç¨å° f æ¹æ³å建çå®ä¾ï¼ä½æ¯ç±äº f æ¹æ³è¿å¨æ§è¡ä¸ï¼æ以å¯è½ä¼åºç°æ¥éé®é¢ãæ以å®æ¹éç¨çæ¯Lock + DoubleCheck çæ¹å¼ã
if?atomic.CompareAndSwapUint(&o.done,?0,?1)?{ f()}æå±æ§è¡å¼å¸¸åå¯ç»§ç»æ§è¡çOnce
çæäºæºç ä¹åï¼æ们就å¯ä»¥æ©å± sync.Once å äºãä¾å¦ f æ¹æ³å¨æ§è¡çæ¶åæ¥éäºï¼ä¾å¦è¿æ¥åå§å失败ï¼æä¹åï¼æ们å¯ä»¥å®ç°ä¸ä¸ªé«çº§çæ¬ç Once å ï¼å ·ä½ç slowDo 代ç å¯ä»¥åèä¸é¢çå®ç°
func?(o?*Once)?slowDo(f?func()?error)?error?{ o.m.Lock()defer?o.m.Unlock()var?err?errorif?o.done?==?0?{ ?//?Double?Checkerr?=?f()if?err?==?nil?{ ?//?没æå¼å¸¸çæ¶åè®°å½doneå¼atomic.StoreUint(&o.done,?1)}}return?err}带æ§è¡ç»æç Once
ç±äº Once æ¯ä¸å¸¦æ§è¡ç»æçï¼æ们ä¸ç¥é Once ä»ä¹æ¶åä¼æ§è¡ç»æï¼å¦æåå¨å¹¶åï¼éè¦ç¥éæ¯å¦æ§è¡æåçè¯ï¼å¯ä»¥çä¸ä¸é¢çæ¡ä¾ï¼æè¿éæ¯ä»¥ redis è¿æ¥çé®é¢æ¥è¿è¡è¯´æçãDo æ¹æ³æ§è¡å®æ¯åå° init å¼è®¾ç½®æ 1 ï¼ç¶åå ¶ä» goruntine å¯ä»¥éè¿ IsConnetion æ¥è·åè¿æ¥æ¯å¦å»ºç«ï¼ç¶åååç»çæä½ã
type?RedisConn?struct?{ once?sync.Onceinit?uint}func?(this?*RedisConn)?Init()?{ this.once.Do(func()?{ ?//?do?redis?connection?atomic.StoreUint(&this.init,?1)})}func?(this?*RedisConn)?IsConnect()?bool?{ ?//?å¦å¤ä¸ä¸ªgoroutinereturn?atomic.LoadUint(&this.init)?!=?0}2024-11-28 19:05281人浏览
2024-11-28 18:021853人浏览
2024-11-28 17:42821人浏览
2024-11-28 17:29905人浏览
2024-11-28 17:05941人浏览
2024-11-28 17:00985人浏览
台北市議員鍾沛君日前揭露,名嘴朱學恒曾在飯局時強吻她,還說:「反正妳明天也不會記得」,引發眾怒。對此,朱學恒昨12)日表示,已經向台北地檢署「自己告發自己」,願主動面對司法調查,期藉由均署之偵查,早日
現代人講求保健養生,中醫養生歷史悠久,也常有民眾尋中醫讓身體更健康。在古籍《黃帝內經》中,就提到很多養生方法。那麼中醫養生方法都有哪些妙方,既可以幫助大家身體健康還能延年益壽?養生方式多元 持之以恆最