歡迎光臨
每天分享高質量文章

Java Web技術經驗總結(六)

1. synchronized的作用和原理:link

  • 使用經驗:synchronized是一種互斥鎖。在Java開發中,當某個變數需要在多個執行緒之間共享時,需要分析具體的場景:如果多個執行緒對該共享變數的讀和寫之間沒有競爭關係,則可以考慮使用concurrent包下提供的併發資料結構,例如ConcurrentHashMap;但是,如果多個執行緒對共享變數之間的讀和寫動作之間有競態關係,則需要將整個變數鎖住。

  • 作用:(1)確保多執行緒之間互斥訪問共享變數;(2)確保共享變數的修改能夠及時可見;(3)有效解決重排序問題。

  • 原理:synchronized是Java的內建鎖。JVM透過 monitorenter和 monitorexit指令實現內建鎖。

    • 每個物件都有一個監視器鎖(monitor),當monitor被佔用時,該物件就處於鎖定狀態,其他試圖訪問該物件的執行緒將阻塞;

    • 對於同一個執行緒來說,monitor是可重入的,重入的時候會將“佔用數”+1;

    • 當一個執行緒試圖訪問某個變數時,如果發現該變數的monitor佔用數為0,則可以佔用該物件;如果>=1,則進入阻塞。

    • 執行 monitorexit的執行緒必須是某個物件的monitor的所有者,當執行完該指令之後,如果佔用數為0,則當前執行緒釋放該monitor。

2. volatile的原理:link

  • 當我們宣告共享變數為volatile後,對這個變數的讀/寫將會很特別。理解volatile特性的一個好方法是:把對volatile變數的單個讀/寫,看成是使用同一個監視器鎖對這些單個讀/寫操作做了同步。

  • volatile的強度比synchronized弱,即對於volatile變數的多個讀/寫操作之間的沒有約束力。這個可以類比於我們用synchronized修飾某個HashMap物件和使用ConcurrentHashMap之間的關係。

  • 特性總結

    • 可見性。對一個volatile變數的讀,總是能看到(任意執行緒)對這個volatile變數最後的寫入。

    • 原子性:對任意單個volatile變數的讀/寫具有原子性,但類似於volatile++這種複合操作不具有原子性

  • 原理:加記憶體屏障,確保執行緒在讀某個變數之前,將該執行緒的私有快取失效,直接從記憶體中讀;確保執行緒在寫某個變數之後,將該執行緒私有快取刷入記憶體。

3. 分散式session服務的實現

在之前參加過的一個專案中,負責session服務的重寫(C++轉Java),站在更高的層面看,為什麼需要這個session服務呢?是為瞭解決分散式系統中,多臺機器之間的session同步問題(參考:分散式session中同步的那些事)。

  • 有狀態的session和無狀態的session之間如何選擇?有狀態的session,指的是使用者的資訊會被編碼到sid中;無狀態的session,則是該sid僅僅是隨機字串,沒有包含任何有效資訊。在分散式系統中,要根據業務特點選擇有狀態和無狀態session:含有使用者資訊的,適用於網站登入等經常登入登出的場景;不含使用者資訊的,適用於使用者登入操作不頻繁,其他業務操作比較頻繁的。

  • 分散式系統中的CAP理論

4. Spring MVC

@ResponseBody和HttpMessageConverter的實現原理?或者,換個問法:Spring MVC中自動傳回JSON、XML或者其他型別的資料的方式?這個問題我參考了SpringMVC關於json、xml自動轉換的原理研究[附帶原始碼分析],並根據自己目前所用的4.2.6.RELEASE版本過了一遍原始碼。

  • 配置方法,在xxxx-servlet.xml檔案中新增mvc配置;然後使用@ResponseBody修飾Controller中的一個方法。

  • 原理分析

    • 在上使用Command + B快捷鍵,跳轉到該標簽的定義檔案,即spring-mvc-4.0.xsd,可以看到關於該標簽的定義,在這個檔案中有一行 name="message-converters"minOccurs="0">,表示該標簽內部可以提供一個巢狀標簽

    • 用於設定HttpMessageConverters。
    • 在Spring的容器中,對bean的處理分為兩步:(1)讀取元資料配置(XML檔案、JavaConfig或者註解),生成BeanDefinition物件;(2)透過各種BeanDefinitionParser的具體實現,生成我們定義的bean物件。

    • 這裡負責解析標簽的解析器是AnnotationDrivenBeanDefinitionParser。在該類的parse方法中,實體化了RequestMappingHandlerMapping、ConfigurableWebBindingInitializer、RequestMappingHandlerAdapter等類。其中,RequestMappingHandlerMapping負責定義url請求和具體的Controller方法直接的對映關係;RequestMappingHandlerAdapter負責作為配接器樣式出現,填平DispatchServlet與不同RequestMappingHandler之間的關係;

  • RequestMappingHandlerAdapter中有一個屬性messageConverters,就是我們這裡要講到的訊息轉換器。在AnnotationDrivenBeanDefinitionParser這個類中有一個方法:getMessageConverters,程式碼如下:

  1. private ManagedList> getMessageConverters(Element element,

  2. Object source, ParserContext parserContext) {

  3.   Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");

  4.   ManagedList super Object> messageConverters = new ManagedList<Object>();

  5.   if (convertersElement != null) {

  6.      messageConverters.setSource(source);

  7.      for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {

  8.         Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);

  9.         messageConverters.add(object);

  10.      }

  11.   }

  12.   if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {

  13.      messageConverters.setSource(source);

  14.      messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

  15.      RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);

  16.      stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);

  17.      messageConverters.add(stringConverterDef);

  18.      messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));

  19.      messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));

  20.      messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

  21.      if (romePresent) {

  22.         messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));

  23.         messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));

  24.      }

  25.      if (jackson2XmlPresent) {

  26.         RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);

  27.         GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);

  28.         jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);

  29.         jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);

  30.         messageConverters.add(jacksonConverterDef);

  31.      }

  32.      else if (jaxb2Present) {

  33.         messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));

  34.      }

  35.      if (jackson2Present) {

  36.         RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);

  37.         GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);

  38.         jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);

  39.         messageConverters.add(jacksonConverterDef);

  40.      }

  41.      else if (gsonPresent) {

  42.         messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));

  43.      }

  44.   }

  45.   return messageConverters;

  46. }

這個函式中的關鍵是幾個If…else…陳述句,透過判斷指定的類是否存在,來決定是否新增對應的messageConverter(在4.0之後應該可以使用@Condition條件註解來最佳化這塊程式碼)。

  1. private static final boolean jackson2XmlPresent =

  2.      ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

  • 另外一方面,在Spring MVC的請求處理流程中,由RequestMappingHandlerAdapter實現具體的handler的呼叫,即handleInternal函式,在這個函式中,該類將具體的方法呼叫委託給了HandlerMethod的invokeHandle方法處理;在這個方法中又接著向下委託給具體的ServletInvocableHandlerMethod類的invokeAndHandle方法處理。

  • 在ServletInvocableHandlerMethod這個類中維護了一個類:HandlerMethodReturnValueHandlerComposite。

  1. private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

  • 透過returnValueHandlers呼叫handleReturnValue方法,利用多型特性,找到具體的HandlerMethodReturnValueHandler實現去處理。這裡採用的是:RequestResponseBodyMethodProcessor ,即如下程式碼:

  1. @Override

  2. public void handleReturnValue(Object returnValue, MethodParameter returnType,

  3.      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)

  4.      throws IOException, HttpMediaTypeNotAcceptableException,

  5. HttpMessageNotWritableException {

  6.         mavContainer.setRequestHandled(true);

  7.         // Try even with null return value. ResponseBodyAdvice could get involved.

  8.         writeWithMessageConverters(returnValue, returnType, webRequest);

  9. }

  • 具體的寫HTTP響應的方法就是writeWithMessageConverters,這個方法的主要內容是:(1)獲得客戶端可接受的媒體型別串列,即從HTTP request中拿到Accept引數;(2)獲得伺服器中定義的可提供的媒體型別;(3)將這兩個集合做交集,最終得到一個compatibleMediaTypes集合(如果該集合為空,則則丟擲異常);(4)canwrite方法根據returnValueClass和selectedMediaType決定是否可以用某個轉換器輸出。

5. SSM(Spring MVC、Spring、MyBatis)專案中進行單元測試時,如何希望配置Log4j

可以參考這篇文章:link

6. 在專案中,遇到JVM中CPU過高的情況,如何處理?

我一般遵循如下步驟排查:

  • 透過 ps-ef|grep'java'命令找到jvm的PID,例如12345;

  • 透過 top-H-p12345命令檢視每個執行緒的工作狀態,截圖;

  • 透過 jstack-l12345>temp.txtdump執行緒棧

  • 將第二步中截圖留下的前幾個執行緒的執行緒號,轉換成16進位制,在temp.txt中查詢,就能找到對應的執行緒棧。

今天看到宏江前輩提供的一個指令碼:檢測最耗cpu的執行緒的指令碼,準備下次遇到類似問題的時候試試。

贊(0)

分享創造快樂