范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

spring循环依赖三级缓存

  循环依赖
    BeanFactory作为bean工厂管理我们的单例bean,那么肯定需要有个缓存来存储这些单例bean,在Spring中就是一个Map结构的缓存,key为beanName,value为bean。在获取一个bean的时候,先从缓存中获取,如果没有获取到,那么触发对象的实例化和初始化操作,完成之后再将对象放入缓存中,这样就能实现一个简单的bean工厂。
    由于Spring提供了依赖注入的功能,支持将一个对象自动注入到另一个对象的属性中,这就可能出现循环依赖(区别于dependsOn)的问题:A对象在创建的时候依赖于B对象,而B对象在创建的时候依赖于A对象。
  可以看到,由于A依赖B,所以创建A的时候触发了创建B,而B又依赖A,又会触发获取A,但是此时A正在创建中,还不在缓存中,就引发了问题。多级缓存一级缓存
    前面引出了循环依赖的问题,那么该如何解决呢?其实很简单,单单依赖一级缓存就能解决。对于一级缓存,我们不再等对象初始化完成之后再存入缓存,而是等对象实例化完成后就存入一级缓存,由于此时缓存中的bean还没有进行初始化操作,可以称之为早期对象,也就是将A对象提前暴露了。这样B在创建过程中获取A的时候就能从缓存中获取到A对象,最后在A对象初始化工作完成后再更新一级缓存即可,这样就解决了循环依赖的问题。
    但是这样又引出了另一个问题:早期对象和完整对象都存在于一级缓存中,如果此时来了其它线程并发获取bean,就可能从一级缓存中获取到不完整的bean,这明显不行,那么我们不得已只能在从一级缓存获取对象处加一个互斥锁,以避免这个问题。
    而加互斥锁也带来了另一个问题,容器刷新完成后的普通获取bean的请求都需要竞争锁,如果这样处理,在高并发场景下使用spring的性能一定会极低。二级缓存
    既然只依赖一级缓存解决循环依赖需要靠加锁来保证对象的安全发展,而加锁又会带来性能问题,那该如何优化呢?答案就是引入另一个缓存。这个缓存也是一个Map结构,key为beanName,value为bean,而这个缓存可以称为二级缓存。我们将早期对象存到二级缓存中,一级缓存还是用于存储完整对象(对象初始化工作完成后),这样在接下来B创建的过程中获取A的时候,先从一级缓存获取,如果一级缓存没有获取到,则从二级缓存获取,虽然从二级缓存获取的对象是早期对象,但是站在对象内存关系的角度来看,二级缓存中的对象和后面一级缓存中的对象(指针)都指向同一对象,区别是对象处于不同的阶段,所以不会有什么问题。
    既然获取bean的逻辑是先从一级缓存获取,没有的话再从二级缓存获取,那么也可能出现其它线程获取到不完整对象的问题,所以还是需要加互斥锁。不过这里的加锁逻辑可以下沉到二级缓存,因为早期对象存储在二级缓存中,从一级缓存获取对象不用加锁,这样的话当容器初始化完成之后,普通的getBean请求可以直接从一级缓存获取对象,而不用去竞争锁。当循环依赖遇上AOP
    似乎二级缓存已经解决了循环依赖的问题,看起来也非常简单,但是不要忘记Spring提供的另一种特性:AOP。Spring支持以CGLIB和JDK动态代理的方式为对象创建代理类以提供AOP支持,在前面总结bean生命周期的文章中已经提到过,代理对象的创建(通常)是在bean初始化完成之后进行(通过BeanPostProcessor后置处理器)的,而且按照正常思维来看,一个代理对象的创建也应该在原对象完整的基础上进行,但是当循环依赖遇上了AOP就不那么简单了。
    还是在前面A和B相互依赖的场景中,试想一下如果A需要被代理呢?由于二级缓存中的早期对象是原对象,而代理对象是在A初始化完成之后再创建的,这就导致了B对象中引用的A对象不是代理对象,于是出现了问题。
    要解决这问题也很简单,把代理对象提前创建不就行了?也就是如果没有循环依赖,那么代理对象还是在初始化完成后创建,如果有循环依赖,那么就提前创建代理对象。那么怎么判断发生了循环依赖呢?在B创建的过程中获取A的时候,发现二级缓存中有A,就说明发生了循环依赖,此时就为A创建代理对象,将其覆盖到二级缓存中,并且将代理对象复制给B的对应属性,解决了问题。当然,最终A初始化完成之后,在一级缓存中存放的肯定是代理对象。
    如果在A和B相互依赖的基础上,还有一个对象C也依赖了A:A依赖B,B依赖A,A依赖C,C依赖A。那么在为A创建代理对象的时候,就要注意不能重复创建。
  可以在对象实例化完成之后,将其beanName存到一个Set结构中,标识对应的bean正在创建中,而当其他对象创建的过程依赖某个对象的时候,判断其是否在这个set中,如果在就说明发生了循环依赖。三级缓存
    虽然仅仅依靠二级缓存能够解决循环依赖和AOP的问题,但是从解决方案上看来,维护代理对象的逻辑和getBean的逻辑过于耦合,Spring没有采取这种方案,而是引入了另一个三级缓存。三级缓存的key还是为beanName,但是value是一个函数(ObjectFactory#getBean方法),在该函数中执行获取早期对象的逻辑:getEarlyBeanReference方法。 在getEarlyBeanReference方法中,Spring会调用所有
  SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,通过该方法可以修改早期对象的属性或者替换早期对象。这个是Spring留给开发者的另一个扩展点,虽然我们很少使用,不过在循环依赖遇到AOP的时候,代理对象就是通过这个后置处理器创建。Spring三级缓存源码实现
    那么三级缓存在spring中是如何体现的呢?我们来到
  DefaultSingletonBeanRegistry中://一级缓存,也就是单例池,存储最终对象 private final Map singletonObjects = new ConcurrentHashMap<>(256); //二级缓存,存储早期对象 private final Map earlySingletonObjects = new HashMap<>(16); //三级缓存,存储的是一个函数接口, private final Map> singletonFactories = new HashMap<>(16);
    其实所谓三级缓存,在源码中就是3个Map,一级缓存用于存储最终单例对象,二级缓存用于存储早期对象,三级缓存用于存储函数接口。
    在容器刷新时调用的十几个方法中,
  finishBeanFactoryInitialization方法主要用于实例化单例bean,在其逻辑中会冻结BeanDefinition的元数据,接着调用beanFactory的preInstantiateSingletons方法,循环所有被管理的beanName,依次创建Bean,我们这里主要关注创建bean的逻辑,也就是AbstractBeanFactory的doGetBean方法(该方法很长,这里只贴了部分代码):protected  T doGetBean(final String name, @Nullable final Class requiredType, 			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { 		//解析FactoryBean的name(&)和别名 		final String beanName = transformedBeanName(name); 		Object bean; 		//尝试从缓存中获取对象 		Object sharedInstance = getSingleton(beanName); 		if (sharedInstance != null && args == null) { 			//包含了处理FactoryBean的逻辑,可以通过&+beanName获取原对象,通过beanName获取真实对象 			//FactoryBean的真实bean有单独的缓存factoryBeanObjectCache(Map结构)存放 			//如果是普通的bean,那么直接返回对应的对象 			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); 		} 		else { 			//只能解决单例对象的循环依赖 			if (isPrototypeCurrentlyInCreation(beanName)) { 				throw new BeanCurrentlyInCreationException(beanName); 			} 			//如果存在父工厂,并且当前工厂中不存在对应的BeanDefinition,那么尝试到父工厂中寻找 			//比如spring mvc整合spring的场景 			BeanFactory parentBeanFactory = getParentBeanFactory(); 			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { 				//bean的原始名称 				String nameToLookup = originalBeanName(name); 				if (parentBeanFactory instanceof AbstractBeanFactory) { 					return ((AbstractBeanFactory) parentBeanFactory).doGetBean( 							nameToLookup, requiredType, args, typeCheckOnly); 				} 				else if (args != null) { 					return (T) parentBeanFactory.getBean(nameToLookup, args); 				} 				else { 					return parentBeanFactory.getBean(nameToLookup, requiredType); 				} 			} 			try { 			 				//处理dependsOn的依赖(不是循环依赖) 				String[] dependsOn = mbd.getDependsOn(); 				if (dependsOn != null) { 					for (String dep : dependsOn) { 						if (isDependent(beanName, dep)) { 							//循环depends-on 抛出异常 							throw new BeanCreationException(mbd.getResourceDescription(), beanName, 									"Circular depends-on relationship between "" + beanName + "" and "" + dep + """); 						} 						registerDependentBean(dep, beanName); 						try { 							getBean(dep); 						} 						catch (NoSuchBeanDefinitionException ex) { 							throw new BeanCreationException(mbd.getResourceDescription(), beanName, 									""" + beanName + "" depends on missing bean "" + dep + """, ex); 						} 					} 				} 				//创建单例bean 				if (mbd.isSingleton()) { 					//把beanName 和一个ObjectFactory类型的singletonFactory传入getSingleton方法 					//ObjectFactory是一个函数,最终创建bean的逻辑就是通过回调这个ObjectFactory的getObject方法完成的 					sharedInstance = getSingleton(beanName, () -> { 						try { 							//真正创建bean的逻辑 							return createBean(beanName, mbd, args); 						} 						catch (BeansException ex) { 							destroySingleton(beanName); 							throw ex; 						} 					}); 					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 				} 			} 			catch (BeansException ex) { 				cleanupAfterBeanCreationFailure(beanName); 				throw ex; 			} 		} 		return (T) bean; 	}
    对于单例对象,调用doGetBean方法的时候会调用getSingleton方法,该方法的入参是beanName和一个ObjectFactory的函数,在getSingleton方法中通过回调ObjectFactory的getBean方法完成bean的创建,那我们来看看getSingleton方法(部分代码):	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { 		Assert.notNull(beanName, "Bean name must not be null"); 		//互斥锁 		synchronized (this.singletonObjects) { 			//首先尝试从尝试从单例缓存池中获取对象,如果获取到了对象则直接返回,否则进入创建的逻辑 			Object singletonObject = this.singletonObjects.get(beanName); 			if (singletonObject == null) { 				//在创建之前将要创建的beanName存入singletonsCurrentlyInCreation中,这个是一个Map结构,用于标识单例正在创建中 				beforeSingletonCreation(beanName); 				boolean newSingleton = false; 				boolean recordSuppressedExceptions = (this.suppressedExceptions == null); 				if (recordSuppressedExceptions) { 					this.suppressedExceptions = new LinkedHashSet<>(); 				} 				try { 					//回调singletonFactory的getObject方法,该函数在外层doGetBean方法中传入 					//实际调用了createBean方法 					singletonObject = singletonFactory.getObject(); 					newSingleton = true; 				} finally { 					if (recordSuppressedExceptions) { 						this.suppressedExceptions = null; 					} 					//将beanName从正在创建单例bean的集合singletonsCurrentlyInCreation中移除 					afterSingletonCreation(beanName); 				} 				if (newSingleton) { 					//将创建好的单例bean放入一级缓存,并且从二级缓存、三级缓存中移除 					//添加到registeredSingletons中 					addSingleton(beanName, singletonObject); 				} 			} 			return singletonObject; 		} 	}
    getSingleton方法的主线逻辑很简单,就是通过传入的函数接口创建单例bean,然后存入一级缓存中,同时清理bean在二级缓存、三级缓存中对应的数据。前面已经分析过了,传入的函数接口调用的是createBean方法,那么我们又来到createBean方法,createBean方法主要调用doCreateBean方法,在doCreateBean调用之前会先调用
  nstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation拦截bean的实例化,如果这里的后置处理器返回了bean,则不会到后面的doCreateBean方法中,不过我们这里不用关心这个逻辑,直接跳到doCreateBean方法(部分代码):protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) 			throws BeanCreationException { 		BeanWrapper instanceWrapper = null; 		if (mbd.isSingleton()) { 			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); 		} 		if (instanceWrapper == null) { 			//创建bean,也是就是实例化,会把实例化后的bean包装在BeanWrapper中 			instanceWrapper = createBeanInstance(beanName, mbd, args); 		} 		//eanWrapper中获取到对象 		final Object bean = instanceWrapper.getWrappedInstance(); 		Class<?> beanType = instanceWrapper.getWrappedClass(); 		if (beanType != NullBean.class) { 			mbd.resolvedTargetType = beanType; 		} 		synchronized (mbd.postProcessingLock) { 			if (!mbd.postProcessed) { 				try { 					//MergedBeanDefinitionPostProcessor后置处理器的处理 					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); 				} 				catch (Throwable ex) { 					throw new BeanCreationException(mbd.getResourceDescription(), beanName, 							"Post-processing of merged bean definition failed", ex); 				} 				mbd.postProcessed = true; 			} 		} 		//判断该对象是否支持提前暴露,核心条件就是要求单例对象 		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 				isSingletonCurrentlyInCreation(beanName)); 		if (earlySingletonExposure) { 			//把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,该方法内部调用getEarlyBeanReference方法 			//将早期对象存入三级缓存,三级缓存存的是一个接口实现(ObjectFactory接口),其getBean方法会调用getEarlyBeanReference方法 			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 		} 		// Initialize the bean instance. 		Object exposedObject = bean; 		try { 			//属性赋值操作,这里就可能出现循环依赖问题 			populateBean(beanName, mbd, instanceWrapper); 			//实例化操作:调用三个Aware、后置处理器beforeInitialization(包含@PostConstruct的实现)、afterPropertiesSet、init-method、后置处理器afterInitialization等 			//个方法里的后置处理器可能会改变exposeObject 			exposedObject = initializeBean(beanName, exposedObject, mbd); 		} 		catch (Throwable ex) { 			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { 				throw (BeanCreationException) ex; 			} 			else { 				throw new BeanCreationException( 						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); 			} 		} 		if (earlySingletonExposure) { 			//这里的入参为false,表示只从一级缓存和二级缓存中获取 			//非循环依赖的情况下,不会调用到三级缓存,三级缓存中的接口会在单例对象入一级缓存的时候移除 			Object earlySingletonReference = getSingleton(beanName, false); 			if (earlySingletonReference != null) { 					//如果获取到了早期暴露对象earlySingletonReference不为空 					if (exposedObject == bean) { 						//如果exposeObject在经过initializeBean方法后没有改变,那么说明 						//没有被后置处理器修改,那么用earlySingletonReference替换exposeObject 						exposedObject = earlySingletonReference; 				} 			} 		} 		return exposedObject; 	}
    这个方法的逻辑就是先实例化对象,如果对象支持暴露早期对象,那么会将早期对象作为入参传入getEarlyBeanReference方法,然后包装成一个ObjectFactory存入三级缓存,接着再调用populateBean方法填充对象的属性。而在填充对象属性的过程中,就可能发现循环依赖,比如当前正在创建A,然后在populateBean方法中发现了依赖B,那么就会调用getBean(B),B也会走上面A走的这个流程,当B也走到populateBean方法填充属性的时候,又发现依赖了A,那么又会调用getBean(A)。那么在populateBean创建A的时候,会从三级缓存中获取bean,如果A需要被代理,那么会创建代理对象,这样B中的A属性就是代理对象了,接着把三级缓存获取的对象存入二级缓存中。
  在B处理完成之后就回到了A的逻辑,假设populateBean(A)的逻辑完成了,那么接着进入initializeBean方法进行A的初始化操作,注意这里执行初始化操作的对象是A原对象,代理对象存储在二级缓存中。由于initializeBean方法会调用后置处理器的
  before-afterInitialization方法,这个后置处理器可能会改变对象,所以在后面的逻辑中,如果从二级缓存中获取到了A的代理对象,会判断原对象经过后置处理器后有没有变化,如果还是原对象,那么用二级缓存中的代理对象覆盖原对象,所以doCreateBean方法返回的就是代理对象,最终存入一级缓存中。流程就是:创建A实例对象->设置A的B属性->创建B实例对象->设置B的A属性->创建A的代理对象存入二级缓存->初始化A对象->从二级缓存取出A的代理对象覆盖A对象->A的代理对象存入一级缓存
    我们需要关注的就是在B的populateBean逻辑里调用getBean(A)是什么样的逻辑。当然也是getBean->doGetBean->getSingleton这个逻辑,我们着重关注一下getSingleton方法的实现:protected Object getSingleton(String beanName, boolean allowEarlyReference) { 		//首先从一级缓存中获取 		Object singletonObject = this.singletonObjects.get(beanName); 		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 			//如果从一级缓存没有获取到,并且获取的对象正在创建中,这里已经能够确定发生了循环依赖 			synchronized (this.singletonObjects) { 				//为了防止其它线程获取到不完整工单对象,这里使用同步锁控制 				//从二级缓存中获取早期对象 				singletonObject = this.earlySingletonObjects.get(beanName); 				if (singletonObject == null && allowEarlyReference) { 					//如果二级缓存中也没有获取到,且allowEarlyReference为true(这里传入的是true) 					//那么尝试从三级缓存中获取 					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 					if (singletonFactory != null) { 						//从三级缓存中获取到了对象,注意三级缓存存储的是ObjectFactory 						//调用getObject方法,前面提到了早期暴露的对象存入三级缓存的ObjectFactory的getBean方法调用 						//的是getEarlyBeanReference方法,所以这里会调用getEarlyBeanReference方法 						singletonObject = singletonFactory.getObject(); 						//把早期对象存入二级缓存 						this.earlySingletonObjects.put(beanName, singletonObject); 						//三级缓存中的接口没用了,直接移除 						this.singletonFactories.remove(beanName); 					} 				} 			} 		} 		return singletonObject; 	}
    在getSingleton方法的逻辑中,先从一级缓存获取,如果一级缓存没有找到,那么如果获取的bean正在创建中,则从二级缓存获取,如果二级缓存没有找到,那么从三级缓存获取,三级缓存中存的是ObjectFactory实现,最终会调用其getBean方法获取bean,然后存入二级缓存中,同时清除三级缓存。同时提供了一个allowEarlyReference参数控制是否能从三级缓存中获取。
    对于循环依赖的情况,getBean(A)->存入正在创建缓存->存入三级缓存->populateBean(A)->getBean(B)->populateBean(B)->getBean(A)->getSingleton(A),当在populateBean(B)的过程中调用getSingleton(A)的时候,明显一级缓存和二级缓存都为空,但是三级缓存不为空,所以会通过三级缓存获取bean,三级缓存的创建逻辑如下:		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 				isSingletonCurrentlyInCreation(beanName)); 		if (earlySingletonExposure) { 			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 		}
    所以我们直接来到getEarlyBeanReference方法获取早期对象:	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { 		Object exposedObject = bean; 		//如果容器中有InstantiationAwareBeanPostProcessors后置处理器 		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { 			for (BeanPostProcessor bp : getBeanPostProcessors()) { 				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { 					//找到SmartInstantiationAwareBeanPostProcessor后置处理器 					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; 					//调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法 					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); 				} 			} 		} 		return exposedObject; 	}
    这个getEarlyBeanReference方法的逻辑很简单,但是非常重要,该方法主要就是对
  SmartInstantiationAwareBeanPostProcessor后置处理器的调用,而循环依赖时的AOP就是通过这个SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法实现的,相关的具体类是AnnotationAwareAspectJAutoProxyCreator,这个留待AOP原理分析的文章中详细说明。总结
    多级缓存并不只是为了解决循环依赖和AOP的问题,还考虑到了逻辑的分离、结构的扩展性和保证数据安全前提下的效率问题等。由于存在二级缓存提前暴露不完整对象的情况,所以为了防止getSingleton方法返回不完整的bean,在该方法中使用了synchronized加锁,而为了不影响容器刷新完成后正常获取bean,从一级缓存获取对象没有加锁,事实上也不用加锁,因为一级缓存中要么没有对象,要么就是完整的对象。
  循环依赖发生在对象都属性赋值,也就是populateBean阶段,在对象实例化完成后将其包装成一个函数接口ObjectFactory存入三级缓存,通过getEarlyBeanReference方法触发
  SmartInstantiationAwareBeanPostProcessor后置处理器的执行,如果循环依赖遇上了AOP,那么就在这里进行处理。
    这里每个单例对象实例化之后存入三级缓存,即使没有发生循环依赖,这个是为之后可能发生的循环依赖做准备,如果最终没有发生循环依赖,那么对象实例化之后,正常初始化,然后存入一级缓存即可,而在存入一级缓存的时候会清空二级和三级缓存。

国家中小学智慧教育平台资源提供和使用情况作为职称评聘重要依据中国教育报中国教育新闻网北京3月1日讯(记者高毅哲欧媚)记者从教育部今天举行的发布会上获悉,对国家中小学智慧教育平台的资源提供和使用情况,将作为教学成果评定职称评聘和评优评先等方面国家中小学智慧教育平台亮相六大板块资源超2万条新华网北京3月1日电记者从今日召开的教育部新闻发布会现场获悉,教育部在总结国家中小学网络云平台运行服务经验的基础上,研究制定了国家中小学智慧教育平台建设与应用方案,并将原云平台改版只需两步,让你的iPhone日历显示国家节假日安排iPhone的日历虽然标注了节假日,但并没有显示具体放假安排。比如,显示5月1日是劳动节却没有显示放假多少天,几号调休等信息。我们通过一些小设置,就可以让iPhoneiPad显示国独立影像NPU双OIS防抖,FindX5Pro影像表现竟丝毫不输苹果三星?2月24日,OPPOFindX5系列新品正式发布,凭借一机双芯首款NPU芯片哈苏色彩加持等亮点,果不其然吸引了极高的关注度。不过相比这些常规升级,更多人关注FindX5Pro影像方苹果成功申请ARVR头戴式显示器专利DoNews3月2日消息(李文朋)3月2日消息,苹果公司申请的面部接合部和头戴式显示器专利3月1日获得授权。专利摘要显示,该头戴式显示器佩戴在用户的头部,面部接合部接合用户的眼睛上这一点苹果真的落后安卓太多,iphone14Typec终于要来了?近日外媒有消息称,苹果将在今年的iphone14系列上,把传统的Lightning接口换成Typec。消息一出立刻引起果粉们的惊呼,不少人表示终于等到这一天了。一个接口有那么重要吗因为这4点,我准备卖掉苹果13ProMax,入手三星S22UItra以前跟朋友聚餐用的都是苹果,但是这一次我换成了三星,而且还是三星S22UItra(测试机),这应该是我近半年来,头一次把苹果放在家中,并把三星S22UItra当成了主力机来使用,对OPPO旗下6000mAh新机曝光,最大存储版本仅1899元对于OPPO旗下的诸多机型,大家听说过最多的恐怕就是OPPOFind系列和Reno系列,一个是集成了性能和影像的高端旗舰,一个是拥有超高颜值的人气机型。不过大多数用户更加注重的还是lsblk命令详解lsblk命令用来查看block设备的信息。主要应用场景获取wwnid,获取块设备列表,获取块设备类型(ssd,hdd),获取块设备的size等信息。数据来源sysdevblock我的跨境电商日志7亚马逊新手选品,一定要避开这些坑我是山东花和尚,一名亚马逊新手,我会不定时分享自己的亚马逊历程,让你避开那些我曾经踩过的坑。如果我可以,我相信,那你一定也可以。敬请关注留言评论,谢谢新手,特别是小白新手做亚马逊,网约车这个曾经迎风撒尿的少年终于来到了迟暮之年(季前赛)倒推几年甚至几十年对于黑车这个概念事实上每个人都或多或少的有一些了解!它渗透着我们个人的生活比如身边开着黑车每天在小区门口趴活的邻居大哥在门口抽着烟可以管你要每公里10元的价格爱走
为什么很多程序员不用switch,而是大量的ifelseif?答案主要因为switch不适合业务系统的实际复杂需求,业务不断的变更迭代,一更改需求,条件的复杂度高了,switch无力处理。switch优点那么什么时候适合switch,它的场景杨元庆说科学无国界,你认同这句话吗?科学无国界?!如果这话真是杨元庆说的,那我只能跟他说,无知,太无知了!如果说,科学真的是无国界的,那为啥美国的航天科学家不来我们这,帮助我们建造航天飞船?那为啥俄罗斯的航空科学家不国产芯片再传好消息,打破西方垄断,已掌握3个关键制造环节国产芯片掌握3个关键制造环节技术国外掌握大量的芯片制造技术,在芯片制造领域,很多顶级的设备,材料都需要从国外进口。因为对国外有非常大的芯片依赖,导致我国每年的芯片进口额都在3000最新升级华为鸿蒙2。0的方法,一分钟告诉你设备支不支持全文共计665字,阅读时间约为5分钟华为真的牛皮,被谷歌卡住脖子后,真就自己研发出来了自己的操作系统和生态圈没错就是最近频频上榜,登上热搜的鸿蒙操作系统harmonyOS2。0城河长电科技完成收购ADI新加坡测试工厂AMD发布3DChiplet架构戴姆勒同意支付诺基亚专利使用费新闻速递三分钟了解产业大事1hr长电科技完成收购ADI新加坡测试工厂6月1日消息,长电科技宣布,已正式完成对AnalogDevicesInc。(以下简称ADI)新加坡测试厂房的收购,该厂房华为周跃峰没有安全健康的数据产业做支持,数字经济就是空中楼阁数据安全的存在意义是保证数据在全生命周期得到妥善保护,最终实现价值变现并促进数字经济健康发展。我们身处一个高度信息化的社会,如果企业机构对数据没有采取保护,无异于一种自杀行为。华为深圳一特斯拉车主被困车内险窒息,特斯拉回应近日,深圳一特斯拉车主经历了大概是人生中最难忘的一劫,烈日炎炎,他竟被困在车里长达15分钟,差点虚脱又又又见特斯拉,一起来看看是怎么回事被困车内险窒息5月30日上午,文先生像往常一荣耀618首日惊喜不断智慧屏开售1小时销量破万荣耀50官宣6月1日,荣耀官方公布了今年618的首日成绩。荣耀智慧屏战绩非常引人瞩目,开售1小时全平台狂卖上万台,天猫平台只用了40分钟销量销售额就超去年全天,无疑已经成为618的大热爆款。从千元机便宜但千万别买错!618销量火爆的三款手机,最低只要599元618已经正式拉开了手机的销量争夺大战,各大手机厂商都在摩拳擦掌地提升自家的手机销量,同时也在抢占手机市场。这个时候是入手手机的最佳时刻,因为通过各大手机的销量,就可以看出来哪些手未来5年,到底是混合动力汽车更热一点,还是纯电动汽车?现今社会,汽车是每个人都避不过的话题。有车一族或者要置换汽车,还没购车的人或者在预谋着人生的第一辆汽车。不管是哪一种,都应该了解一下,未来几年中国汽车市场可能的变化!首先抛出一个结微软将于6月24日公布下一代Windows系统上线时间来源环球网据外媒6月3日报道,微软今天正式宣布将会在6月24日举办发布会,发布会上将正式宣布下一代Windows系统的上线时间。届时,微软CEO与首席产品官均将出席这次发布会。微软