Java Web技术经验总结(六)

  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的线程的脚本,准备下次遇到类似问题的时候试试。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

muduo 4 网络库学习之Exception类、Thread 类封装中的知识点(重点讲pthread_atfork())

class Exception : public std::exception

1601
来自专栏分布式系统进阶

Kafka集群Metadata管理Kafka源码分析-汇总

可以看到是调用了ReplicaManager.maybeUpdateMetadataCache方法, 里面又会调用到MetadataCache.updateCa...

2642
来自专栏Android相关

处理器结构--ReorderBuffer

Reorder Buffer用来保存在乱序执行之前的(OOOE)指令执行顺序,当指令集合在乱序执行后按照原有指令顺序将结果提交。

1474
来自专栏向治洪

android内存优化

刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但...

2499
来自专栏芋道源码1024

面试问烂的 Spring AOP 原理

来源:https://www.jianshu.com/p/e18fd44964eb

2324
来自专栏Jack-Cui

Linux应用层系统时间写入RTC时钟的方法

Linux内核版本:linux-3.0.35 开发板:i.MX6S MY-IMX6-EK200 系统:Ubuntu12 前言:之前写过一篇关于如...

2240
来自专栏何俊林

金三银四,Android高级开发面试题目,帮你助力

最近金三银四,相信不少朋友都在跃跃欲动,看看市场机会,连夜整理了一波Android高级开发面试题目,帮你助力! Java基础 1、内部类的作用 内部类可以用多个...

40310
来自专栏Android机动车

我的图片四级缓存框架

开发App一定涉及到图片加载、图片处理,那就必须会用到三方的图片框架,要么选择自己封装。至于主流的三方图片框架,就不得不说老牌的ImageLoader、如今更流...

1493
来自专栏aoho求索

Spring Cloud Bus中的事件的订阅与发布(一)

年前最后一篇文章,提前祝大家新年快乐! 下面进入正文。Spring Cloud Bus用轻量级的消息代理将分布式系统的节点连接起来。这可以用来广播状态的该表(比...

45510
来自专栏xingoo, 一个梦想做发明家的程序员

【插件开发】—— 4 SWT编程须知

  根据前两篇博文,应该对插件开发有所了解。 前文回顾: 1 插件学习篇 2 简单的建立插件工程以及模型文件分析 3 利用扩展点,开发透视图   S...

2275

扫码关注云+社区

领取腾讯云代金券