Java Web技术经验总结(六)

作者: 程序熊大 | 来源:发表于2016-06-16 22:39 被阅读2937次
    1. synchronized的作用和原理:link
    • 使用经验:synchronized是一种互斥锁。在Java开发中,当某个变量需要在多个线程之间共享时,需要分析具体的场景:如果多个线程对该共享变量的读和写之间没有竞争关系,则可以考虑使用concurrent包下提供的并发数据结构,例如ConcurrentHashMap;但是,如果多个线程对共享变量之间的读和写动作之间有竞态关系,则需要将整个变量锁住。
    • 作用:(1)确保多线程之间互斥访问共享变量;(2)确保共享变量的修改能够及时可见;(3)有效解决重排序问题。
    • 原理:synchronized是Java的内置锁。JVM通过monitorentermonitorexit指令实现内置锁。
      • 每个对象都有一个监视器锁(monitor),当monitor被占用时,该对象就处于锁定状态,其他试图访问该对象的线程将阻塞;
      • 对于同一个线程来说,monitor是可重入的,重入的时候会将“占用数”+1;
      • 当一个线程试图访问某个变量时,如果发现该变量的monitor占用数为0,则可以占用该对象;如果>=1,则进入阻塞。
      • 执行monitorexit的线程必须是某个对象的monitor的所有者,当执行完该指令之后,如果占用数为0,则当前线程释放该monitor。
    1. volatile的原理:link
    • 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别。理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步。
    • volatile的强度比synchronized弱,即对于volatile变量的多个读/写操作之间的没有约束力。这个可以类比于我们用synchronized修饰某个HashMap对象和使用ConcurrentHashMap之间的关系。
    • 特性总结
      • 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
      • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性
    • 原理:加内存屏障,确保线程在读某个变量之前,将该线程的私有缓存失效,直接从内存中读;确保线程在写某个变量之后,将该线程私有缓存刷入内存。
    1. 分布式session服务的实现
      在之前参加过的一个项目中,负责session服务的重写(C++转Java),站在更高的层面看,为什么需要这个session服务呢?是为了解决分布式系统中,多台机器之间的session同步问题(参考:分布式session中同步的那些事)。
    • 有状态的session和无状态的session之间如何选择?有状态的session,指的是用户的信息会被编码到sid中;无状态的session,则是该sid仅仅是随机字符串,没有包含任何有效信息。在分布式系统中,要根据业务特点选择有状态和无状态session:含有用户信息的,适用于网站登录等经常登入登出的场景;不含用户信息的,适用于用户登录操作不频繁,其他业务操作比较频繁的。
    • 分布式系统中的CAP理论
    1. Spring MVC中@ResponseBody和HttpMessageConverter的实现原理?或者,换个问法:Spring MVC中自动返回JSON、XML或者其他类型的数据的方式?这个问题我参考了SpringMVC关于json、xml自动转换的原理研究[附带源码分析],并根据自己目前所用的4.2.6.RELEASE版本过了一遍源码。
    • 配置方法,在xxxx-servlet.xml文件中添加mvc配置;然后使用@ResponseBody修饰Controller中的一个方法。
    <mvc:annotation-driven/>
    
    • 原理分析
      • 在<mvc:annotation-driven/>上使用Command + B快捷键,跳转到该标签的定义文件,即spring-mvc-4.0.xsd,可以看到关于该标签的定义,在这个文件中有一行<xsd:element name="message-converters" minOccurs="0">,表示该标签内部可以提供一个嵌套标签<message-converters>,用于设置HttpMessageConverters。
      • 在Spring的容器中,对bean的处理分为两步:(1)读取元数据配置(XML文件、JavaConfig或者注解),生成BeanDefinition对象;(2)通过各种BeanDefinitionParser的具体实现,生成我们定义的bean对象。
      • 这里负责解析<mvc:annotation-driven />标签的解析器是AnnotationDrivenBeanDefinitionParser。在该类的parse方法中,实例化了RequestMappingHandlerMapping、ConfigurableWebBindingInitializer、RequestMappingHandlerAdapter等类。其中,RequestMappingHandlerMapping负责定义url请求和具体的Controller方法直接的映射关系;RequestMappingHandlerAdapter负责作为适配器模式出现,填平DispatchServlet与不同RequestMappingHandler之间的关系;
    • RequestMappingHandlerAdapter中有一个属性messageConverters,就是我们这里要讲到的消息转换器。在AnnotationDrivenBeanDefinitionParser这个类中有一个方法:getMessageConverters,代码如下:
    private ManagedList<?> getMessageConverters(Element element,
     Object source, ParserContext parserContext) {
       Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
       ManagedList<? super Object> messageConverters = new ManagedList<Object>();
       if (convertersElement != null) {
          messageConverters.setSource(source);
          for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
             Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
             messageConverters.add(object);
          }
       }
       if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
          messageConverters.setSource(source);
          messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
          RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
          stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
          messageConverters.add(stringConverterDef);
          messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
          messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
          messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
          if (romePresent) {
             messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
             messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
          }
          if (jackson2XmlPresent) {
             RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
             GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
             jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
             jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
             messageConverters.add(jacksonConverterDef);
          }
          else if (jaxb2Present) {
             messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
          }
          if (jackson2Present) {
             RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
             GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
             jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
             messageConverters.add(jacksonConverterDef);
          }
          else if (gsonPresent) {
             messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
          }
       }
       return messageConverters;
    }
    

    这个函数中的关键是几个If...else...语句,通过判断指定的类是否存在,来决定是否添加对应的messageConverter(在4.0之后应该可以使用@Condition条件注解来优化这块代码)。

    private static final boolean jackson2XmlPresent =
          ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
    
    • 另外一方面,在Spring MVC的请求处理流程中,由RequestMappingHandlerAdapter实现具体的handler的调用,即handleInternal函数,在这个函数中,该类将具体的方法调用委托给了HandlerMethod的invokeHandle方法处理;在这个方法中又接着向下委托给具体的ServletInvocableHandlerMethod类的invokeAndHandle方法处理。
      • 在ServletInvocableHandlerMethod这个类中维护了一个类:HandlerMethodReturnValueHandlerComposite。
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    
    - 通过returnValueHandlers调用handleReturnValue方法,利用多态特性,找到具体的HandlerMethodReturnValueHandler实现去处理。这里采用的是:*RequestResponseBodyMethodProcessor* ,即如下代码
    
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
          ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
          throws IOException, HttpMediaTypeNotAcceptableException,
     HttpMessageNotWritableException {
    
             mavContainer.setRequestHandled(true);
             // Try even with null return value. ResponseBodyAdvice could get involved.
             writeWithMessageConverters(returnValue, returnType, webRequest);
    }
    
    - 具体的写HTTP响应的方法就是writeWithMessageConverters,这个方法的主要内容是:(1)获得客户端可接受的媒体类型列表,即从HTTP request中拿到Accept参数;(2)获得服务器中定义的可提供的媒体类型;(3)将这两个集合做交集,最终得到一个compatibleMediaTypes集合(如果该集合为空,则则抛出异常);(4)canwrite方法根据returnValueClass和selectedMediaType决定是否可以用某个转换器输出。
    
    1. SSM(Spring MVC、Spring、MyBatis)项目中进行单元测试时,如果希望配置Log4j,可以参考这篇文章:link

    2. 在项目中,遇到JVM中CPU过高的情况,如何处理?

    • 我一般遵循如下步骤排查:
      • 通过ps -ef | grep 'java'命令找到jvm的PID,例如12345;
      • 通过top -H -p12345命令查看每个线程的工作状态,截图;
      • 通过jstack -l 12345 > temp.txtdump线程栈
      • 将第二步中截图留下的前几个线程的线程号,转换成16进制,在temp.txt中查找,就能找到对应的线程栈。
    • 今天看到宏江前辈提供的一个脚本:检测最耗cpu的线程的脚本,准备下次遇到类似问题的时候试试。

    相关文章

      网友评论

      • 48892085f47c:请问下黄底代码样式是怎么弄的?简书编辑器没找见这个功能
        48892085f47c:@杜琪 多谢多谢,简书新手还不知道又markdown功能 :sweat_smile:
        程序熊大:@程序猿小屌丝 你用的是markdown编辑器嘛?
      • StongFang:本来准备打赏,看你声明那么没出息,放弃了
      • SuperS:加油

      本文标题:Java Web技术经验总结(六)

      本文链接:https://www.haomeiwen.com/subject/cpzydttx.html