最近線上遇到幾個小問題,排查代碼發現基本都是些細節問題,做些總結提示大家不要掉到坑中。
一、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
由於本人經驗有限,文章中難免會有錯誤,請瀏覽文章的您指正或有不同的觀點共同探討!