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,程式碼如下:
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決定是否可以用某個轉換器輸出。
5. SSM(Spring MVC、Spring、MyBatis)專案中進行單元測試時,如何希望配置Log4j
可以參考這篇文章:link
6. 在專案中,遇到JVM中CPU過高的情況,如何處理?
我一般遵循如下步驟排查:
-
透過
ps-ef|grep'java'
命令找到jvm的PID,例如12345; -
透過
top-H-p12345
命令檢視每個執行緒的工作狀態,截圖; -
透過
jstack-l12345>temp.txt
dump執行緒棧 -
將第二步中截圖留下的前幾個執行緒的執行緒號,轉換成16進位制,在temp.txt中查詢,就能找到對應的執行緒棧。
今天看到宏江前輩提供的一個指令碼:檢測最耗cpu的執行緒的指令碼,準備下次遇到類似問題的時候試試。