持續更新線上問題及解決方案


     最近線上遇到幾個小問題,排查代碼發現基本都是些細節問題,做些總結提示大家不要掉到坑中。

一、fastjson的序列化SerializerFeature使用注意                                       

    我們都知道Integer、Double、Boolean等包裝類型的字段默認值是null。如果不對這些字段設置值,那么在反序列化時得到的相應的值也應該是null。

1、本次服務接口升級后導致調用方業務邏輯判斷失敗,比如Integer type 在升級之前是返回的null,而升級后返回了數字0。究其原因發現同事在序列化時設置了SerializerFeature.WriteNullNumberAsZero,而導致Number類型(Boolean,Integer,Float,Double等)都轉成了0。

2、null屬性不顯示

我們先來看一段代碼

Map <String , Object > map = new HashMap<String , Object>();
map.put("a",1);
map.put("b","");
map.put("c",null);

String str = JSONObject.toJSONString(map);
System.out.println(str);//輸出結果:{"a":1,"b":""}

屬性c,怎么就憑空消失了哪?這里不講述原因,只說明解決方案。

解決方案:使用fastjson的SerializerFeature序列化屬性,即JSONObject.toJSONString(Object object, SerializerFeature... features)

因此上面的代碼就可以改寫成

String str = JSONObject.toJSONString(map,SerializerFeature.WriteMapNullValue);

 

我們常用的SerializerFeature:

  • SerializerFeature.WriteMapNullValue, //輸出空置的字段
  • SerializerFeature.WriteNonStringKeyAsString,//如果key不為String 則轉換為String 比如Map的key為Integer
  • SerializerFeature.WriteNullListAsEmpty,//list為null時輸出[] 
  • SerializerFeature.WriteNullNumberAsZero,//number為null時輸出0
  • SerializerFeature.WriteNullStringAsEmpty,//String為null時輸出""
  • SerializerFeature.WriteNullBooleanAsFalse,//boolean為null時輸出false
  • SerializerFeature.QuoteFieldNames,//輸出key時是否使用雙引號,默認為true
  • SerializerFeature.DisableCheckSpecialChar//一個對象的字符串屬性中如果有特殊字符如雙引號,將會在轉成json時帶有反斜杠轉移符。如果不需要轉義,可以使用這個屬性。默認為false 

    總結:

    1、服務接口的測試用例覆蓋率還是太低

    2、對於通用處理工具,在做修改時對工具類的前因后果要清楚

二、java.util.List.subList的陷阱                                                             

     我們一般都會使用java.util.List中有一個subList方法,返回一個以fromIndex為起始索引(包含),以toIndex為終止索引(不包含)的一部分的視圖(List)。    

List<E> subList(int fromIndex, int toIndex);

    之所以說是視圖,是因為實際上,返回的list是靠原來的list支持的。原來的list和返回的list做的“非結構性修改”(non-structural changes),都會影響到彼此對方。

所謂的“非結構性修改”,是指不涉及到list的大小改變的修改。相反,結構性修改,指改變了list大小的修改。

如果發生結構性修改的是返回的子list,那么原來的list的大小也會發生變化;

如果發生結構性修改的是原來的list(不包括由於返回的子list導致的改變),那么會是拋出一個ConcurrentModificationException。

  • 如何刪除一個list中的某個值   
list.remove(index)
  • 如何刪除一個list的某個區段
list.subList(int fromIndex, int toIndex).clear();
  • 如何修改子list視圖而不影響原來的list或修改原list而不影響子list視圖
List<Integer> subList2 = new ArrayList<Integer>(list2.subList(2, list2.size()));

 那么ArrayList的remove的底層是怎么做的?

      AbstractList中有一個屬性modCount,這個屬性是跟蹤list中數據被修改的次數,任何對list的add/remove操作,都將導致modCount++。

在AbstractList中還有一個內部類Itr implements Iterator,Itr是一個list遍歷的工具類。當然list.iterator()方法也是返回Itr對象,在Itr中有一個校驗位屬性expectedModCount;對於一個itr對象,其初始時expectedModCount=modCount。

      Iterator是list一個視圖,其最終還是操作list的存儲結構。在使用iterator遍歷時,remove()操作,會導致modCount++(AbstractList.remove()),但是還有expectedModCount=modCount,即在iterator中remove數據,會帶來expectedModCount與modCount值的同步

在Iterator遍歷時,next(),remove()方法會校驗expectedModCount與modCount值是否一致,如果不一致,就意味着這list數據在iterator外部被修改,此時iterator遍歷將會造成ConcurrentModificationException.

      AbstractLlist不僅支持普通的iterator,還支持ListIterator(ArrayList,LinkedList均支持),ListIterator增加了遍歷時雙向游標能力(previous,next),增加了add方法。add方法和remove方法一樣也做了expectedModCount和modCount一致性校驗.

我們來看下面四個對list數據刪除的代碼的區別

1)
for(int i=0;i<list.size();i++){
     list.remove(i);
}

2)
for(int i=list.size()-1;i>=0;i--){
    list.remove(i);
}

3)
int size = list.size();
for(int i=size-1;i>-1;i--){
     list.remove(i);
}

4)
for(Object i : list){
   //如果list中存在多個Object互相equals時,此方法仍然有效.注意list.remove(Object)內部使用了遍歷操作,並使用equals來比較對象並刪除.
   list.remove(i);
}

5) 
Iterator it = list.iterator()
while(it.hasNext()){
        it.next();
        it.remove();
}

1),2),3)是最普通的遍歷方式,但是在遍歷並有刪除操作時,似乎它們執行的結果還有些差距,根據坐標刪除,

那么1)實事上只會有一半被刪掉,1)中每刪除一次,計算一次list.size(),但是當前i++,且前端刪除會造成數組結構copy。

2)后端刪除,不會造成copy,每次都是刪除最后一個位置,直至結束

3)因為size沒有重新計算,在刪除一半數據后,拋出IndexOutOfBoundsException

4)/5)正常

  

三、illegal character: \65279                                                               

   問題產生的操作過程,同事使用SVN提交java文件,發現有沖突使用UltraEdit進行了修改,重新編譯時卻報了異常

java:[1,0] illegal character: \65279 

   解決方法

   將文件重新保存成UTF-8 無BOM即可。

               

具體原因參看高人的解釋

http://blog.csdn.net/shixing_11/article/details/6976900

 

四、Java線程池任務執行完畢后線程回收的問題 

我們知道ThreadPoolExecutor解決了兩個重要的問題:

1、由於減少了每個任務調用的開銷,它們通常可以在執行大量異步任務時提供增強的性能

2、還可以提供綁定和管理資源(包括執行任務集時使用的線程)的方法。 

當使用java中的ThreadPoolExecutor,給我們的工作帶來方便的同時,如果不當使用同樣也帶來巨大潛在危險。

最近在review代碼時,發現線程池中的所有任務執行完畢后,線程並沒有被銷毀。我們知道初始化ThreadPoolExecutor會有構造參數

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

我們看下TreadPoolExecutor是怎樣工作的

注意

      1、核心線程即core線程,只有當前線程數小於等於corePoolSize時,這時的線程才叫核心線程。

      2、在新任務被提交時,如果運行的core線程少於corePoolSize,才創建新core線程。並不是一開始就創建corePoolSize個core線程。

      3、如果運行的線程多於corePoolSize 而少於 maximumPoolSize,則僅當隊列滿時才創建新線程。

      
按需構造
      核心線程最初只是在新任務到達時才被ThreadPoolExecutor創建和啟動的,但是也可以手動調用方法 prestartCoreThread() 或 prestartAllCoreThreads()來的提前啟動核心線程。
    如果構造帶有非空隊列的池,這時則可能希望預先啟動線程。


保持活動時間

      如果線程池中當前線程數大於corePoolSize ,則這些多出的線程在空閑時間超過 keepAliveTime 時將會終止。
      如果后來線程池中線程變得更為活動,則可以創建新的線程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 動態地更改此參數,如果把值設為Long.MAX_VALUE TimeUnit.NANOSECONDS 的話,空閑線程不會被回收直到ThreadPoolExecutor為Terminate。
      默認情況下,此種活動策略只在有多於corePoolSize Threads的線程時才會應用。但是只要 keepAliveTime 值非 0,也可以通過allowCoreThreadTimeOut(boolean) 方法也可將此超時策略應用於核心線程。
注意1:setKeepAliveTime(long, java.util.concurrent.TimeUnit)用於設置空閑線程最長的活動時間,即如果空閑時間超過設定值,就停止該線程,對該線程進行回收。
    該策略默認只對非內核線程有用(即當前線程數大於corePoolSize),可以調用allowCoreThreadTimeOut(boolean)方法將此超時策略擴大到核心線程
注意2:如果把值設為Long.MAX_VALUE TimeUnit.NANOSECONDS的話,空閑線程不會被回收直到ThreadPoolExecutor為Terminate。

 

線程終止

      如果ThreadPoolExecutor在程序中沒有任何引用且沒有任何活動線程,線程池也不會自動 shutdown。
      如果希望確保回收線程(即使用戶忘記調用 shutdown()),則必須安排未使用的線程最終終止,設置適當保持活動時間,設置 allowCoreThreadTimeOut(boolean)。

    

關鍵函數

  • public void shutdown()

    按過去執行已提交任務的順序發起一個有序的關閉,但是不接受新任務。如果已經關閉,則調用沒有其他作用。
    拋出:
        SecurityException - 如果安全管理器存在並且關閉此 ExecutorService 可能操作某些不允許調用者修改的線程(因為它沒有 RuntimePermission("modifyThread")),或者安全管理器的 checkAccess 方法拒絕訪問。

  • public List<Runnable> shutdownNow()

    嘗試停止所有的活動執行任務、暫停等待任務的處理,並返回等待執行的任務列表。在從此方法返回的任務隊列中排空(移除)這些任務。
    並不保證能夠停止正在處理的活動執行任務,但是會盡力嘗試。 此實現通過 Thread.interrupt() 取消任務,所以無法響應中斷的任何任務可能永遠無法終止。
    返回:
        從未開始執行的任務的列表。 
    拋出:
        SecurityException - 如果安全管理器存在並且關閉此 ExecutorService 
        可能操作某些不允許調用者修改的線程(因為它沒有 RuntimePermission("modifyThread")),
        或者安全管理器的 checkAccess 方法拒絕訪問。

  • public int prestartAllCoreThreads()

    啟動所有核心線程,使其處於等待工作的空閑狀態。僅當執行新任務時,此操作才重寫默認的啟動核心線程策略。
    返回:
        已啟動的線程數

  • public boolean allowsCoreThreadTimeOut()

    如果此池允許核心線程超時和終止,如果在 keepAlive 時間內沒有任務到達,新任務到達時正在替換(如果需要),則返回 true。當返回 true 時,適用於非核心線程的相同的保持活動策略也同樣適用於核心線程。當返回 false(默認值)時,由於沒有傳入任務,核心線程不會終止。
    返回:
        如果允許核心線程超時,則返回 true;否則返回 false

  • public void allowCoreThreadTimeOut(boolean value)

    如果在保持活動時間內沒有任務到達,新任務到達時正在替換(如果需要),則設置控制核心線程是超時還是終止的策略。當為 false(默認值)時,由於沒有傳入任務,核心線程將永遠不會中止。當為 true 時,適用於非核心線程的相同的保持活動策略也同樣適用於核心線程。為了避免連續線程替換,保持活動時間在設置為 true 時必須大於 0。通常應該在主動使用該池前調用此方法。
    參數:
        value - 如果應該超時,則為 true;否則為 false 
    拋出:
        IllegalArgumentException - 如果 value 為 true 並且當前保持活動時間不大於 0。

我們了解了ThreadPoolExecutor后,確保線程的回收就可以通過以下方式

// 在allowCoreThreadTimeOut設置為true時,ThreadPoolExecutor的keepAliveTime參數必須大於0。
executor.allowCoreThreadTimeOut(true);
// 在任務執行完后,調用shutdown方法,將線程池中的空閑線程回收
executor.shutdown();

五、Eclipse下maven項目自動打war包丟失jar包問題解決方法 

 由於本地沒有maven倉庫和強大的“牆”,使用maven命令clean package卻打包失敗。沒有辦法使用了Eclipse最原始的Export命令,打好包后發現依賴的jar都沒有打進去,真是一波三折呀。現說下完整的打包過程和解決方法:

1、選擇相應的profile,沒有配置略過

2、.project文件

檢查<buildSpec>節點中是否包含如下節點

<buildCommand>                    
     <name>
         org.eclipse.m2e.core.maven2Builder
     </name>
     <arguments></arguments>
</buildCommand>

檢查<natures>節點中是否包含如下節點

<nature>org.eclipse.m2e.core.maven2Nature</nature> 

3、.classpath文件

檢查是否包含如下節點

<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
        <attributes>
            <attribute name="maven.pomderived" value="true"/>
            <attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
        </attributes>
</classpathentry>

正因為沒有這個屬性<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>,才導致打的包中沒有jar。

上述三步驟檢查完畢后,重新clean工程,再打包就可以正常了。

六、Linux系統下忽略表明大小寫

坑爹的DBA將線上的一個數據庫重做后,導致了某些服務拋出了大量的異常

select name,type,updateTime from VIDEO_INFO where 1=1  ExecuteData Table 'MS.VIDEO_INFO' doesn't exist

信息提示找不到表明。

我們都知道Linux是區分表明大小寫,windows不區分表明大小寫的。DBA重做了新的數據庫后卻忽略設置表明大小寫。

解決方法很簡單:

編輯mysql配置文件:vi /etc/my.cnf

添加:lower_case_table_names=1 一句到文件中。

重啟MySQL,服務一切正常。

小結:重要的事情說一遍,檢查一遍。

七、swap file "*.swp" already exists!

Linux下我們使用vi或vim對文件編輯, 當打開文件或保存文件可能會出現:

swap file "*.swp" already exists!

[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:

原因:

使用vim編輯文件實際是先copy一份臨時文件並映射到內存給你編輯, 編輯的是臨時文件, 當執行:w后才保存臨時文件到原文件,執行:q后才刪除臨時文件。

每次啟動檢索式否有臨時文件, 有則詢問如何處理,就會出現如上情景。

解決方法:

1、顯示隱藏文件 ll -a或ls -a

2、刪除隱藏文件 rm -f *.swp

 

八、20880端口被占用

問題描述:
在部署線上服務時,出現20880端口被占用而無法啟動。

問題分析:
20880端口被該服務器上的客戶端隨機選取源端口給占用掉了。

解決方案:

1、使用net.ipv4.ip_local_port_range參數,規划出一段端口段預留作為服務的端口,這種方法是可以解決當前問題,但是會有個問題,端口使用量減少了,當服務器需要消耗大量的端口號的話,比如反代服務器,就存在瓶頸了。
2、將服務監聽的端口以逗號分隔全部添加到ip_local_reserved_ports中,TCP/IP協議棧從ip_local_port_range中隨機選取源端口時,會排除ip_local_reserved_ports中定義的端口,因此就不會出現端口被占用了服務無法啟動。、

推薦使用第二種方法

$ cat /proc/sys/net/ipv4/ip_local_port_range
32000 61000
$ cat /proc/sys/net/ipv4/ip_local_reserved_ports
8080,9148 

 

 

 

 

由於本人經驗有限,文章中難免會有錯誤,請瀏覽文章的您指正或有不同的觀點共同探討!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM