目前面试遇到的所有的问题总结(三家)

aop应用场景,什么是aop?

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

AOP使用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

AOP相关概念

方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

Spring AOP组件

下面这种类图列出了Spring中主要的AOP组件

img

如何使用Spring AOP

可以通过配置文件或者编程的方式来使用Spring AOP。

配置可以通过xml文件来进行,大概有四种方式:

\1. 配置ProxyFactoryBean,显式地设置advisors, advice, target等

\2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象

\3. 通过aop:config来配置

\4. 通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点

也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象

具体使用的示例可以google. 这里略去

Spring AOP代理对象的生成

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:

  1. /**
  2. *
    1. *
    2. 获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false)
    3. *
    4. 检查上面得到的接口中有没有定义 equals或者hashcode的接口
    5. *
    6. 调用Proxy.newProxyInstance创建代理对象
    7. *
    • */
    • public Object getProxy(ClassLoader classLoader) {
    • if (logger.isDebugEnabled()) {
    • ​ logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
    • ​ }
    • ​ Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
    • ​ findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    • return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    • }
    • 那这个其实很明了,注释上我也已经写清楚了,不再赘述。

      下面的问题是,代理对象生成了,那切面是如何织入的?

      我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

      1. publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
      2. ​ MethodInvocation invocation = null;
      3. ​ Object oldProxy = null;
      4. boolean setProxyContext = false;
      5. ​ TargetSource targetSource = this.advised.targetSource;
      6. ​ Class targetClass = null;
      7. ​ Object target = null;
      8. try {
      9. ​ //eqauls()方法,具目标对象未实现此方法
      10. if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
      11. return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
      12. ​ }
      13. ​ //hashCode()方法,具目标对象未实现此方法
      14. if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
      15. return newInteger(hashCode());
      16. ​ }
      17. ​ //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
      18. if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
      19. ​ &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
      20. ​ // Service invocations onProxyConfig with the proxy config…
      21. return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
      22. ​ }
      23. ​ Object retVal = null;
      24. if (this.advised.exposeProxy) {
      25. ​ // Make invocation available ifnecessary.
      26. ​ oldProxy = AopContext.setCurrentProxy(proxy);
      27. ​ setProxyContext = true;
      28. ​ }
      29. ​ //获得目标对象的类
      30. ​ target = targetSource.getTarget();
      31. if (target != null) {
      32. ​ targetClass = target.getClass();
      33. ​ }
      34. ​ //获取可以应用到此方法上的Interceptor列表
      35. ​ List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
      36. ​ //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
      37. if (chain.isEmpty()) {
      38. ​ retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
      39. ​ } else {
      40. ​ //创建MethodInvocation
      41. ​ invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
      42. ​ retVal = invocation.proceed();
      43. ​ }
      44. ​ // Massage return value if necessary.
      45. if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
      46. ​ &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
      47. ​ // Special case: it returned"this" and the return type of the method
      48. ​ // is type-compatible. Notethat we can’t help if the target sets
      49. ​ // a reference to itself inanother returned object.
      50. ​ retVal = proxy;
      51. ​ }
      52. return retVal;
      53. ​ } finally {
      54. if (target != null && !targetSource.isStatic()) {
      55. ​ // Must have come fromTargetSource.
      56. ​ targetSource.releaseTarget(target);
      57. ​ }
      58. if (setProxyContext) {
      59. ​ // Restore old proxy.
      60. ​ AopContext.setCurrentProxy(oldProxy);
      61. ​ }
      62. ​ }
      63. }

      主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

      首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

      1. public ListgetInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
      2. ​ MethodCacheKeycacheKey = new MethodCacheKey(method);
      3. ​ Listcached = this.methodCache.get(cacheKey);
      4. if(cached == null) {
      5. ​ cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
      6. this,method, targetClass);
      7. this.methodCache.put(cacheKey,cached);
      8. ​ }
      9. ​ returncached;
      10. ​ }

      可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。

      下面来分析下这个方法的实现:

      1. /**
      2. * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
      3. * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
      4. * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
      5. */
      6. publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
      7. ​ // This is somewhat tricky… we have to process introductions first,
      8. ​ // but we need to preserve order in the ultimate list.
      9. ​ List interceptorList = new ArrayList(config.getAdvisors().length);
      10. ​ //查看是否包含IntroductionAdvisor
      11. boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
      12. ​ //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
      13. ​ AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
      14. ​ Advisor[] advisors = config.getAdvisors();
      15. for (int i = 0; i <advisors.length; i++) {
      16. ​ Advisor advisor = advisors[i];
      17. if (advisor instanceof PointcutAdvisor) {
      18. ​ // Add it conditionally.
      19. ​ PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
      20. if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
      21. ​ //TODO: 这个地方这两个方法的位置可以互换下
      22. ​ //将Advisor转化成Interceptor
      23. ​ MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
      24. ​ //检查当前advisor的pointcut是否可以匹配当前方法
      25. ​ MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
      26. if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
      27. if(mm.isRuntime()) {
      28. ​ // Creating a newobject instance in the getInterceptors() method
      29. ​ // isn’t a problemas we normally cache created chains.
      30. for (intj = 0; j < interceptors.length; j++) {
      31. ​ interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
      32. ​ }
      33. ​ } else {
      34. ​ interceptorList.addAll(Arrays.asList(interceptors));
      35. ​ }
      36. ​ }
      37. ​ }
      38. ​ } else if (advisor instanceof IntroductionAdvisor){
      39. ​ IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
      40. if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
      41. ​ Interceptor[] interceptors= registry.getInterceptors(advisor);
      42. ​ interceptorList.addAll(Arrays.asList(interceptors));
      43. ​ }
      44. ​ } else {
      45. ​ Interceptor[] interceptors =registry.getInterceptors(advisor);
      46. ​ interceptorList.addAll(Arrays.asList(interceptors));
      47. ​ }
      48. ​ }
      49. return interceptorList;
      50. }

      这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.

      接下来我们再看下得到的拦截器链是怎么起作用的。

      1. if (chain.isEmpty()) {
      2. ​ retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
      3. ​ } else {
      4. ​ //创建MethodInvocation
      5. ​ invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
      6. ​ retVal = invocation.proceed();
      7. ​ }

      ​ 从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码

      1. public Object proceed() throws Throwable {
      2. ​ // We start with an index of -1and increment early.
      3. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
      4. ​ //如果Interceptor执行完了,则执行joinPoint
      5. return invokeJoinpoint();
      6. ​ }
      7. ​ Object interceptorOrInterceptionAdvice =
      8. this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
      9. ​ //如果要动态匹配joinPoint
      10. if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
      11. ​ // Evaluate dynamic method matcher here: static part will already have
      12. ​ // been evaluated and found to match.
      13. ​ InterceptorAndDynamicMethodMatcher dm =
      14. ​ (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
      15. ​ //动态匹配:运行时参数是否满足匹配条件
      16. if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
      17. ​ //执行当前Intercetpor
      18. ​ returndm.interceptor.invoke(this);
      19. ​ }
      20. else {
      21. ​ //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
      22. return proceed();
      23. ​ }
      24. ​ }
      25. else {
      26. ​ // It’s an interceptor, so we just invoke it: The pointcutwill have
      27. ​ // been evaluated statically before this object was constructed.
      28. ​ //执行当前Intercetpor
      29. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
      30. ​ }
      31. }

      spring springmvc 父子容器?

      一般来说,我们在整合Spring和SpringMVC这两个框架中,web.xml会这样写到:

      <!-- 加载spring容器 --> 
      <!-- 初始化加载application.xml的各种配置文件 --> 
      <context-param>    
      	<param-name>contextConfigLocation</param-name> 
      	<param-value>classpath:spring/application-*.xml</param-value>  
      </context-param>  
      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
      </listener>  
      <!-- 配置springmvc前端控制器 -->  
      <servlet>
          <servlet-name>taotao-manager</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation
      , springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
          <init-param>        
      	<param-name>contextConfigLocation</param-name>
              <param-value>classpath:spring/springmvc.xml</param-value>
          </init-param>    
      <load-on-startup>1</load-on-startup>  
      </servlet>
      
      

      首先配置的是Spring容器的初始化加载的application文件,然后是SpringMVC的前端控制器(DispatchServlet),当配置完DispatchServlet后会在Spring容器中创建一个新的容器。其实这是两个容器,Spring作为父容器,SpringMVC作为子容器。对于传统的spring mvc来说,ServletDispatcher对应的容器为子容器,而web.xml中通过ContextLoaderListner的contextConfigLocation属性配置的为父容器。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyDT5XdV-1637132948043)(F:\笔记\面试汇总.assets\20170205102119755)]

      平时我们在项目中注入关系是这样的顺序(结合图来说):在Service中注入Dao(初始化自动注入,利用@Autowired),接着在Controller里注入Service(初始化自动注入,利用@Autowired),看图,这就意味这作为SpringMVC的子容器是可以访问父容器Spring对象的。

      那么问大家一个问题。要是反过来呢,你把Controller注入到Service中能行么?
      肯定是不行的啊!(如图,这也说明了父容器是不能调用子容器对象的)

      如果Dao,Serive,Controller要是都在Spring容器中,无疑上边的问题是肯定的,因为都是在一个bean里,一个容器中。

      1 .问题:为什么不能在Spring中的Service层配置全局扫描?

      例如:一个项目中我总项目的名字叫com.shop,我们在配置applicationContext-service.xml中,包扫描代码如下:

      <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:context="http://www.springframework.org/schema/context"     ...../ 此处省略>    <!-- 扫描包Service实现类 -->    <context:component-scan base-package="com.shop.service"></context:component-scan></beans>
      

      上面所配置的是一个局部扫描,而不是全局扫描。接下来说原因:
      这里就和上面讲到的父子容器有关系,假设我们做了全局扫描那么代码如下:

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"   
       xmlns:context="http://www.springframework.org/schema/context"     ...../ 此处省略>    
      <!-- 扫描包Service实现类 -->    
      <context:component-scan base-package="com.shop.service"></context:component-scan>
      </beans>
      
      

      此时的Spring容器中就会扫描到@Controller,@Service,@Reposity,@Component,此时的图如下[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AZ7aFBE-1637132948047)(F:\笔记\面试汇总.assets\20170205105019778)]

      结合图去看,相当于他们都会放到大的容器中,而这时的SpringMVC容器中没有对象,没有对象就没有Controller,所以加载处理器,适配器的时候就会找不到映射对象,映射关系,因此在页面上就会出现404的错误。

      2.如果不用Spring容器,直接把所有层放入SpringMVC容器中可不可以?

      当然可以,如果没有Spring容器,我们是可以把所有层放入SpringMVC的。单独使用这个容器是完全可以的,而且是轻量级的。

      3.那么为什么我们在项目中还要联合用到Spring容器和SpringMVC容器?

      答案是: Spring的扩展性,如果要是项目需要加入Struts等可以整合进来,便于扩展框架。如果要是为了快,为了方便开发,完全可以用SpringMVC框架。

      4.组件分开扫描和直接全扫描的区别

      分开扫描:

      在主容器中(applicationContext.xml),将Controller的注解排除掉
      <context:component-scan base-package=“com”>
      <context:exclude-filter type=“annotation” expression=“org.springframework.stereotype.Controller” />
      </context:component-scan>

      而在springMVC配置文件中将Service注解给去掉
      <context:component-scan base-package=“com”>
      <context:include-filter type=“annotation” expression=“org.springframework.stereotype.Controller” />
      <context:exclude-filter type=“annotation” expression=“org.springframework.stereotype.Service” />
      </context:component-scan>

      因为spring的context是父子容器,所以会产生冲突,由ServletContextListener产生的是父容器,springMVC产生的是子容器,子容器Controller进行扫描装配时装配了@Service注解的实例,而该实例理应由父容器进行初始化以保证事务的增强处理,所以此时得到的将是原样的Service(没有经过事务加强处理,故而没有事务处理能力。

      还有一种方式是将service层改用xml配置,其实这样做也是变相的让springmvc无法扫描service,而只能依赖父窗口也就是ServletContextListener来进行初始化,这样同样被赋予了事务性。

      直接扫描:

      直接扫描比较省事,但是事务回得不到处理,所以在具体的层面上还需要加入注解去声明事务,比如在dao层和service层加入@Transactional

      特殊说明:

      ContextLoaderListener: 创建的容器为父容器(applicationContext.xml)

      DispatcherServlet:创建的容器为子容器(appServlet-context.xml)

      1、问题:同一个类可以被子容器和父容器同时生成实例吗?

      答案:可以,并且是不同的实例

      2、问题:如果一个类子容器(appServlet-context.xml)有实例,父容器(applicationContext.xml)也有实例,getBean 使用的是那个?

      答案:

      1、从父容器取:是父容器的实例,

      2、从子容器取值:优先使用子容器的实例;子容器没有尝试去父容器取

      3、问题:子容器(DispatcherServlet)支持 AOP吗?

      答案:支持

      注意:

      1、要确保对应容器的配置文件(appServlet-context.xml/applicationContext.xml)拥有aop:aspectj-autoproxy/ 等与AOP相关的配置

      2、子容器配置的AOP 植入逻辑,对父容器无效;即配置仅对当前容器的实例有效;

      *本质上是因为创建实例时,因为当前容器有实现BeanPostProcessor接口的AnnotationAwareAspectJAutoProxyCreator 实例,创建bean对象时,生成了AOP代理,才具备AOP功能的。*

      4、问题:子容器是否支持事务?

      答案:支持,同AOP,appServlet-context.xml文件必须有对应的配置

      注意:

      1、仅仅在appServlet-context.xml 配置tx:annotation-driven/ ,非子容器加载的实例没有事务能力

      2、在父子容器都配置 tx:annotation-driven/:是各自独立的事务,毫无关系。本质同AOP的原理


      总结:

      1、避免父子容器拥有共同的实例,是没有必要的使用方式。

      常见的资源初始化、预热多次;

      如果父子容器都有实例,而通过SpringUtil 的方式获取bean,就要看SpringUtil 所在的容器,来获取对应的bean,也容易混乱

      2、事务一般不要放在子容器中,子容器应该仅仅存在 web相关的bean;这也间接说明@Transactional 不应该修饰controller

      3、如果希望对子容器的实例拥有事务能力,需要确保aop:aspectj-autoproxy/ 以及Aspect 配置在子容器中

      springmvc的运行原理?

      1.用户发送请求至前端控制器DispatcherServlet
      2.DispatcherServlet收到请求调用处理器映射器HandlerMapping。
      3.处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
      4.DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
      5.执行处理器Handler(Controller,也叫页面控制器)。
      6.Handler执行完成返回ModelAndView
      7.HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
      8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
      9.ViewReslover解析后返回具体View
      10.DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
      11.DispatcherServlet响应用户。

      mybatis多参调用?

      前言

      现在大多项目都是使用Mybatis了,但也有些公司使用Hibernate。使用Mybatis最大的特性就是sql需要自己写,而写sql就需要传递多个参数。面对各种复杂的业务场景,传递参数也是一种学问。
      下面给大家总结了以下几种多参数传递的方法。

      方法1:顺序传参法
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUdwtWdo-1637132948064)(F:\笔记\面试汇总.assets\20200120143609273.png)]
      #{}里面的数字代表你传入参数的顺序。

      这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

      方法2:@Param注解传参法
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kG12toT-1637132948066)(F:\笔记\面试汇总.assets\20200120143631329.png)]
      #{}里面的名称对应的是注解 @Param括号里面修饰的名称。

      这种方法在参数不多的情况还是比较直观的,推荐使用。

      方法3:Map传参法
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBJI4Y0I-1637132948068)(F:\笔记\面试汇总.assets\20200120143847552.png)]
      #{}里面的名称对应的是 Map里面的key名称。

      这种方法适合传递多个参数,且参数易变能灵活传递的情况。

      方法4:Java Bean传参法
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JiqGWCHs-1637132948070)(F:\笔记\面试汇总.assets\20200120143929157.png)]
      #{}里面的名称对应的是 User类里面的成员属性。

      这种方法很直观,但需要建一个实体类,扩展不容易,需要加属性,看情况使用。

      使用Mapper接口时参数传递方式
      Mybatis在使用Mapper接口进行编程时,其实底层是采用了动态代理机制,表面上是调用的Mapper接口,而实际上是通过动态代理调用的SqlSession的对应方法,如selectOne(),有兴趣的朋友可以查看DefaultSqlSession的getMapper()方法实现,其最终会获得一个代理了Mapper接口的MapperProxy对象。MapperProxy对象在调用Mapper接口方法时会把传递的参数做一个转换,然后把转换后的参数作为入参调用SqlSession对应的操作方法(如selectOne、insert等)。转换过程可以参考MapperMethod的execute()方法实现。简单来说是以下规则:
      1、如果传递过来是单参数,且没有以@Param注解进行命名,则直接将单参数作为真实的参数调用SqlSession的对应方法。
      2、如果传递过来的不是单参数或者是包含以@Param注解进行命名的参数,则会将对应的参数转换为一个Map进行传递。具体规则如下:
      2.1、 会把对应的参数按照顺序以param1、param2、paramN这样的形式作为Key存入目标Map中,第一个参数是param1,第N个参数是paramN。
      2.2、 如果参数是以@Param注解命名的参数,则以@Param指定的名称作为Key存入目标Map中。

      2.3、 如果参数不是以@Param注解命名的,则按照顺序以0、1、N这样的形式作为Key存入目标Map中,第一个参数是0,第N个参数是N。

      jvm?

      大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) , VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack本地方法栈 ),其中Method Area*Heap* 是线程共享的 *,VM **Stack,Native Method Stack 和**Program Counter Register***** 是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。

      首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

      概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区)Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

      加载驱动方法

      1.Class.forName(“com.microsoft.sqlserver.jdbc.SQLServerDriver”);

      \2. DriverManager.registerDriver(new com.mysql.jdbc.Driver());

      3.System.setProperty(“jdbc.drivers”, “com.mysql.jdbc.Driver”);

      malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。
      缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存是,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。

      缺页本身是一种中断,与一般的中断一样,需要经过4个处理步骤:

      1、保护CPU现场

      2、分析中断原因

      3、转入缺页中断处理程序进行处理

      4、恢复CPU现场,继续执行

      但是缺页中断是由于所要访问的页面不存在于内存时,由硬件所产生的一种特殊的中断,因此,与一般的中断存在区别:

      1、在指令执行期间产生和处理缺页中断信号

      2、一条指令在执行期间,可能产生多次缺页中断

      3、缺页中断返回是,执行产生中断的一条指令,而一般的中断返回是,执行下一条指令。

      操作系统中的中断

      中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保存现场后自动去执行相应的处理程序,处理完该事件后再返回中断处继续执行原来的程序。中断一般三类,一种是由CPU外部引起的,如I/O中断、时钟中断,一种是来自CPU内部事件或程序执行中引起的中断,例如程序非法操作,地址越界、浮点溢出),最后一种是在程序中使用了系统调用引起的。而中断处理一般分为中断响应和中断处理两个步骤,中断响应由硬件实施,中断处理主要由软件实施。

      线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。
      当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
      线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
      
      线程同步的方法
      (1)wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
      (2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉  
        InterruptedException异常。
      (3)notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的
        唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
      (4)notityAll ():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,
        而是让它们竞争
      

      JVM、类装载机制、多线程并发、IO、网络

      1.介绍下Collection下的集合类,详细问了HashMap的底层,链表红黑树转化的情况,set为什么不重复

      2.StringBuilder和StringBuffer,多线程下如何使用StringBuilder同时又保证效率

      String a = “abc” ; String b = “a” + “bc” ; String c = “a” ; String d = “bc” 比较true还是false

      3.创建线程方式、线程的几个状态,线程池相关的参数、阻塞休眠,多线程有关的关键字都问到了、Condition、await、Lock锁 、synchronized底层等等…

      juc包下问的挺多的,但是我好多不会…,挺多原理,什么锁升级、重量级锁、轻量级锁

      4.OSI七层模型,三次握手

      5.介绍下Spring框架,IOC底层原理,创建Bean的底层原理,反射的过程…

      6.Redis 持久化RDB、AOF,save和bsave,redis为什么快

      7.说说JVM,我说了内存区域,jdk7和8的不同,又问了类加载机制,详细说下堆,垃圾回收相关算法、垃圾回收器

      8.Mysql底层,我主要说了MyISAM和InnoDB两个引擎,B+树为什么快

      9.spingboot问了什么注解 不记得了,让解释他的执行流程原理什么的

      10.介绍了下项目,他想问kafka和elasticsearch的原理的,我说有写技术只是用了下,没看原理,也就没问了

      还有挺多零零散散的问题不记得了,感觉面试官对项目不太感兴趣,主要还是一些技术原理,而且常规的回答好像不是他太想要的

      感觉掌握的还是不太行,问的深一点就不会了,估计没了,他说基础还是有点薄弱的,

      涉及JVM+数据结构-树、list+shell常用命令+多线程

      死锁的产生和如何避免死锁?

      一、什么是死锁?
      死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。死锁是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。很显然,如果没有外力的作用,那么死锁涉及到的各个进程都将永远处于封锁状态

      二、死锁发生的原因?
      A:java 死锁产生的四个必要条件
      1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
      2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
      3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
      4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

      生死锁时,必然存在一个进程–资源的环形链。
      解决死锁的基本方法
      预防死锁:
      资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
      只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
      可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
      资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
      1、以确定的顺序获得锁

      如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:

      如果此时把获得锁的时序改成:

      那么死锁就永远不会发生。 针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生,该算法在这里就不再赘述了,有兴趣的可以自行了解一下。

      2、超时放弃

      当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZ46wjdH-1637132948072)(F:\笔记\面试汇总.assets\20180922174924551)]

      避免死锁:
      最具有代表性的避免死锁算法是银行家算法。
      银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
      检测死锁
      首先为每个进程和每个资源指定一个唯一的号码;
      然后建立资源分配表和进程等待表。
      解除死锁:
      当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

      剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
      撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
      死锁检测
      1、Jstack命令

      jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

      2、JConsole工具

      Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

      请你谈谈你对JVM的理解?Java8的虚拟机有什么更新

      答:在这里我就不说JVM是java虚拟机了。先谈谈java文件的运行过程一个*.java文件通过编译器编译成字节码文件,然后由JVM的类加载字节码文件,由执行引擎执行。在这期间类加载器加载的数据会与java运行时数据区进行交互。在这里看来JVM内存划分为类加载器,执行引擎,本地方法接口,java运行时数据区。其中java运行时数据区划分为:程序计数器,虚拟机栈,本地方法栈,堆,方法区

      程序计数器:指向当前正在执行线程的字节码地址的指令或行号。属于线程私有的。

      虚拟机栈:存储当前线程运行方法所需的数据指令,访问地址(java独有的)

      Java虚拟机栈划分为四个部分:局部变量表,操作数栈,动态链接,方法出口

      本地方法栈:存储本地方法

      方法区:方法区包含静态变量+类信息+字面量常量+运行时常量池

      堆:对象的新建都在堆区完成,JAVA中的GC主要操作的是这个区域

      堆区在逻辑上划分为:新生代,老年代,永久代(java7),在java8将堆区在逻辑上划分为:新生代,老年代和元空间(Meta Space),元空间默认大小为2M左右,其中新生代又分为Eden区,存活区,存活区由;两块大小相等的内存空间组成(即s0区,s1区),也叫from区和to区,JVM在的堆区在物理上只有两块区:新生代和老年代。Java8的虚拟机的更新:元空间将永久代取代,为什么会更新呢?Oracle整合了Sun公司的Hotspot和BEA公司的Jrocket,从而将永久代去掉用元空间取代,永久代和元空间有什么区别?元空间在Jdk1.8之后才有的,其功能实际上和永久代没区别,唯一的区别在于永久代使用的是JVM的堆内存空间,元空间使用的是物理内存,所以元空间的大小受本地内存影响。

      mysql索引都有哪些?有什么不同?

      • InnoDB引擎的表文件,一共有两个:
        • *.frm 这类文件是表的定义文件。
        • *.ibd 这类文件是数据和索引存储文件。表数据和索引聚集存储,通过索引能直接查询到数据。
      • MyIASM引擎的表文件,一共有三个:
        • *.frm 这类文件是表的定义文件。
        • *.MYD 这类文件是表数据文件,表中的所有数据都保存在此文件中。
        • *.MYI 这类文件是表的索引文件,MyISAM存储引擎的索引数据单独存储。

      MyISAM存储引擎在存储索引的时候,是将索引数据单独存储,并且索引的B+Tree最终指向的是数据存在的物理地址,而不是具体的数据。然后再根据物理地址去数据文件(*.MYD)中找到具体的数据。

      那么当存在多个索引时,多个索引都指向相同的物理地址,MyISAM的存储引擎的索引都是同级别的,主键和非主键索引结构和查询方式完全一样。

      首先InnoDB的索引分为聚簇索引和非聚簇索引,聚簇索引即保存关键字又保存数据,在B+Tree的每个分支节点上保存关键字,叶子节点上保存数据。
      聚簇”的意思是数据行被按照一定顺序一个个紧密地排列在一起存储。一个表只能有一个聚簇索引,因为在一个表中数据的存放方式只有一种,一般是主键作为聚簇索引,如果没有主键,InnoDB会默认生成一个隐藏的列作为主键,

      非聚簇索引,又称为二级索引,虽然也是在B+Tree的每个分支节点上保存关键字,但是叶子节点不是保存的数据,而是保存的主键值。通过二级索引去查询数据会先查询到数据对应的主键,然后再根据主键查询到具体的数据行

      总结:

      • MySQL使用B+Tree作为索引的数据结构,因为B+Tree的深度低,节点保存的关键字多,磁盘IO次数少,从而保证了查询效率更高。
      • B+Tree能够保证MySQL无论是主键索引还是非主键索引的查询效果都是稳定的,每次都要查询到叶子节点才能返回数据,B+Tree的叶子节点的深度是一样的,而且为了更好的支持自增主键,B+Tree的查询节点范围是左闭合右开放。
      • MySQL的MyISAM存储引擎,表数据索引数据是分别放到两个文件中进行存储的,由于它本身的索引的B+Tree的叶子节点指向的表数据所在的磁盘地址,而且索引没有主键和非主键之分,所以分开存储,能够更好的统一管理索引;
      • MySQL的InnoDB存储引擎,表数据索引数据是存储在一个文件中的,因为InnoDB的聚簇索引的叶子节点指向的具体的数据行,而且为了保证查询效果的稳定,InnoDB表中必须要有一个聚簇索引,二级索引在进行索引检索时,会先通过二级索引检索到数据的主键值,再根据主键去聚簇索引中检索到具体的数据。

      什么是B-tree

      B树(英语:B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。

      1.根结点至少有两个子女。

      2.每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m

      3.每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m

      4.所有的叶子结点都位于同一层。

      5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

      B树的应用是文件系统及部分非关系型数据库索引。

      什么是B+tree

      B+ 树是一种树数据结构,通常用于关系型数据库(如Mysql)和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。

      在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。

      b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。

      ThreadLocal简介

      多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

      ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-81jtlccQ-1637132948075)(F:\笔记\面试汇总.assets\1368768-20190613220434628-1803630402.png)]

      package test;
      
      public class ThreadLocalTest {
      
          static ThreadLocal<String> localVar = new ThreadLocal<>();
      
          static void print(String str) {
              //打印当前线程中本地内存中本地变量的值
              System.out.println(str + " :" + localVar.get());
              //清除本地内存中的本地变量
              localVar.remove();
          }
      
          public static void main(String[] args) {
              Thread t1  = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //设置线程1中本地变量的值
                      localVar.set("localVar1");
                      //调用打印方法
                      print("thread1");
                      //打印本地变量
                      System.out.println("after remove : " + localVar.get());
                  }
              });
      
              Thread t2  = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //设置线程1中本地变量的值
                      localVar.set("localVar2");
                      //调用打印方法
                      print("thread2");
                      //打印本地变量
                      System.out.println("after remove : " + localVar.get());
                  }
              });
      
              t1.start();
              t2.start();
          }
      }
      

      实现原理?

      下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为nullimgimg,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的

      TCP的三次握手四次挥手?

      三次握手

      握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:

      (1)首先客户端向服务器端发送一段TCP报文,其中:

      标记位为SYN,表示“请求建立新连接”;序号为Seq=X(X一般为1);随后客户端进入SYN-SENT阶段。(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:

      标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);序号为Seq=y;确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:

      标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。

      在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

      此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

      四次挥手

      )首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

      标记位为FIN,表示“请求释放连接“;序号为Seq=U;随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

      (2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

      标记位为ACK,表示“接收到客户端发送的释放连接的请求”;序号为Seq=V;确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

      前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

      (3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

      标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。序号为Seq=W;确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

      (4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

      标记位为ACK,表示“接收到服务器准备好释放连接的信号”。序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBrg6i8L-1637132948082)(F:\笔记\面试汇总.assets\image-20211116131540211.png)]

      常见的设计模式?

      • 工厂模式与抽象工厂模式 (Factory Pattern)(Abstract Factory Pattern)
      • 单例模式 (Singleton Pattern)
      • 建造者模式 (Builder Pattern)
      • 原型模式 (Prototype Pattern)

      结构型

      • 适配器模式 (Adapter Pattern)
      • 装饰器模式 (Decorator Pattern)
      • 桥接模式 (Bridge Pattern)
      • 外观模式 (Facade Pattern)
      • 代理模式 (Proxy Pattern)
      • 过滤器模式 (Filter、Criteria Pattern)
      • 组合模式 (Composite Pattern)
      • 享元模式 (Flyweight Pattern)

      行为型

      • 责任链模式(Chain of Responsibility Pattern)
      • 观察者模式(Observer Pattern)
      • 模板模式(Template Pattern)
      • 命令模式(Command Pattern)
      • 解释器模式(Interpreter Pattern)
      • 迭代器模式(Iterator Pattern)
      • 中介者模式(Mediator Pattern)
      • 策略模式(Strategy Pattern)
      • 状态模式(State Pattern)
      • 备忘录模式(Memento Pattern)
      • 空对象模式(Null Object Pattern)

      锁?

      synchronize底层原理:

      我的理解是对象都有一个监视器ObjectMonitor,这个监视器内部有很多属性,比如当前等待线程数、计数器、当前所属线程等;其中计数器属性就是用来记录是否已被线程占有,方法执行到monitorenter时,计数器+1,执行到monitorexit时,计数器-1,线程就是通过这个计数器来判断当前锁对象是否已被占用(0为未占用,此时可以获取锁);

      补充:一个synchronize锁会有两个monitorexit,这是保证synchronize能一定释放锁的机制,一个是方法正常执行完释放,一个是执行过程发生异常时虚拟机释放;

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    进击的程序员1

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值