Java高手筆記之業(yè)務開發(fā)常見錯誤100例
在Web環(huán)境中使用ThreadLocal出現(xiàn)數(shù)據(jù)錯亂的坑
原因:線程可能重用侠姑,導致ThreadLocal中的數(shù)據(jù)會串
解決︰用完及時清空數(shù)據(jù),比如可以自定義HandlerInterceptorAdapter逾雄,在preHandle 的時候去設置ThreadLocal,在 afterCompletion時去remove
使用了ConcurrentHashMap 但還是出現(xiàn)了線程安全問題
原因:ConcurrentHashMap只能保證提供的原子性讀寫操作(比如putlfAbsent腻脏、computelfAbsent、replace银锻、compute)是線程安全的
解決︰如果需要確保多個原子性操作整體線程安全永品,需要自己加鎖解決
補充:諸如size、isEmpty和containsValue 等聚合方法击纬,在并發(fā)情況下可能會反映ConcurrentHashMap 的中間狀態(tài)鼎姐。因此在并發(fā)情況下,這些方法的返回值只能用作參考更振,而不能用于流程控制
使用了ConcurrentHashMap 但卻沒有發(fā)揮性能優(yōu)勢
原因:仍然像HashMap那樣使用加鎖的方式,來使用ConcurrentHashMap
解決︰考慮使用computelfAbsent炕桨、putlfAbsent、getOrDefault等API來提升性能
在不合適的場景下使用CopyOnWriteArrayList導致的性能問題
原因::CopyOnWriteArrayList每次修改復制一份數(shù)據(jù)
解決︰讀多寫少的場景才考慮CopyOnWriteArrayList肯腕,寫多的場景考慮ArrayList
代碼加鎖
沒有理清楚線程安全問題的所在點献宫,導致鎖無效
原因1:沒有識別線程安全問題的原因胡亂加鎖
原因2∶鎖是實例級別的,資源是類級別的实撒,無法有效保護
解決∶明確鎖和要保護的資源的關系和范圍
加鎖沒有考慮鎖的粒度姊途,可能導致性能問題
原因:加鎖粒度太大涉瘾,使得大段代碼整體串行執(zhí)行,出現(xiàn)性能問題
解決︰盡可能降低鎖的粒度捷兰,僅對必要的代碼塊甚至是需要保護的資源本身加鎖
加鎖沒有考慮鎖的場景,可能導致性能問題
原因:千篇一律使用寫鎖立叛,可以根據(jù)場景更細化地使用高級鎖
解決1:考慮使用StampedLock的樂觀讀的特性,進—步提高性能
解決2:對于讀寫比例差異明顯的場景贡茅,使用ReentrantReadWriteLock 細化區(qū)分讀寫鎖
解決3:在沒有明確需求的情況下秘蛇,不要輕易開啟公平鎖特性
多把鎖時,要格外小心死鎖問題(VisualVM)
原因:多把鎖相互等待對方釋放顶考,導致死鎖
解決:加鎖的時候考慮順序赁还,按順序加鎖不易死鎖
工具:使用VisualVM的線程Dump,查看死鎖問題并分析死鎖原因
線程池
使用Executors聲明線程池導致兩種類型的OOM
原因1: newFixedThreadPool使用無界隊列村怪,隊列堆積太多數(shù)據(jù)導致OOM
原因2: newCachedThreadPool不限制最大線程數(shù)并且使用沒有任何容量的SynchronousQueue 作為隊列秽浇,容易開啟太多線程導致OOM
解決:手動new ThreadPoolExecutor,根據(jù)需求設置合適的核心線程數(shù)甚负、最大線程數(shù)柬焕、線程回策略、隊列梭域、拒絕策略斑举,并對線程進行明確的命名以方便排查問題
線程池線程管理策略詳解︰如何實現(xiàn)一個更激進的線程池?
原因:Java的線程池傾向于優(yōu)先使用隊列,隊列滿了再開啟更多線程
解決:重寫隊列的 offer方法直接返回false病涨,數(shù)據(jù)不入隊列富玷,并且自定義RejectedExecutionHandler,觸發(fā)拒絕策略的時候再把任務加入隊列;參考Tomcat的 ThreadPoolExecutor和TaskQueue類
沒有復用線程池既穆,導致頻繁創(chuàng)建線程的事故
原因:獲取線程池的方法每次都返回一個newCachedThreadPool赎懦,好在newCachedThreadPool可以閑置回收
解決︰使用靜態(tài)字段定義線程池,線程池務必重用
混用線程池幻工,導致性能問題
原因:IO綁定操作和CPU綁定操作混用一個線程池,前者因為負擔重励两,線程長期處于忙的狀態(tài),導致CPU操作吞吐受到影響
解決∶根據(jù)任務的類型聲明合適的線程池囊颅,不同類型的任務考慮使用獨立線程池
擴展:Java 8的 ParallelStream背后是一個公共線程池当悔,別把IO任務使用ParallelStream來處理
CallerRunsPolicy拒絕策略可能帶來的問題
原因:如果設置CallerRunsPolicy,那么被拒絕的任務會由提交任務的線程運行踢代,可能會在線程池滿載的情況下直接拖垮整個應用
解決:對于Web和Netty場景盲憎,要仔細考慮把任務提交到線程池異步執(zhí)行使用的拒絕策略,除非有明確的需求胳挎,否則不考慮使用CallerRunsPolicy拒絕策略
連接池
你知道常見的Client SDK的API饼疙,有哪3種形式嗎?
形式1:
內(nèi)部帶有連接池的API: SDK內(nèi)部會先自動通過連接池獲取連接(幾乎所有的數(shù)據(jù)庫連接池,都是這一類)
形式2:
連接池和連接分離的API:使用者先通過連接池獲取連接慕爬,再使用連接執(zhí)行操作(Jedis)
形式3:
非連接池的API:非線程安全宏多,需要使用者自己封裝連接池
在多線程環(huán)境下使用Jedis出現(xiàn)的線程安全問題
原因:Jedis是連接池和連接分離的API儿惫,Jedis類代表連接,不能多線程環(huán)境下使用
解決︰每次使用JedisPool先獲取到一個Jedis伸但,然后再調用Jedis 的 API肾请,通過addShutdownHook 來關閉JedisPool
不復用Apache CloseableHttpClient會導致什么問題?(jstack、lsof更胖、Wireshark)
連接池如果不復用铛铁,代價可能會比每次創(chuàng)建單個連接還要大:
1、連接池可能每次都會創(chuàng)建一定數(shù)量的初始連接
2却妨、連接池可能會有一些管理模塊饵逐,需要創(chuàng)建單獨的線程來管理
工具:
1、使用jstack觀察到?jīng)]有復用連接池彪标,會出現(xiàn)大量的Connection evictor線程
2倍权、使用Isof觀察到?jīng)]有復用連接池,會出現(xiàn)大量TCP連接
3捞烟、如果復用CloseableHttpClient薄声,使用wireshark 觀察HTTP請求重用一個TCP連接的過程
小心數(shù)據(jù)庫連接池打滿后出現(xiàn)的性能問題(JConsole)
原因︰數(shù)據(jù)庫連接池最大連接數(shù)設置得太小,很可能會因為獲取連接的等待時間太長题画,導致吞吐量低下甚至超時無法獲取連接
解決:對類似數(shù)據(jù)庫連接池的重要資源進行持續(xù)檢測默辨,并設置一半的使用量作為報警閾值,出現(xiàn)預警后及時擴容
工具:使用JConsole 來觀察Hikari連接池的MBean苍息,監(jiān)控活躍連接缩幸、等待線程等數(shù)據(jù)
擴展:修改配置參數(shù)務必驗證是否生效,并且在監(jiān)控系統(tǒng)中確認參數(shù)是否生效竞思、是否合理,避免明明使用的是Hikari連接池表谊,卻還在調整Druid連接池的參數(shù)的情況
HTTP調用
連接超時和讀取超時的5個認知誤區(qū)
誤區(qū)1:連接超時配置得特別長
分析:連接超時很可能是因為防火墻等原因徹底連接不上,不太會出現(xiàn)連接特別慢的現(xiàn)象盖喷,不用設置太長
誤區(qū)2:排查連接超時問題铃肯,卻沒理清連的是哪里
分析:很可能客戶端連接的是Nginx,不是實際的后端服務传蹈,從后端服務層面排查問題沒用
誤區(qū)3:認為出現(xiàn)了讀取超時,服務端的執(zhí)行就會中斷
分析:客戶端讀取超時步藕,服務端業(yè)務邏輯還會持續(xù)運行惦界,不能隨意假設服務端處理是失敗的
誤區(qū)4:認為讀取超時只是Socket網(wǎng)絡層面的概念,是數(shù)據(jù)傳輸?shù)淖铋L耗時咙冗,故將其配置得非常短
分析:讀取超時并不是數(shù)據(jù)在網(wǎng)絡傳輸?shù)臅r間沾歪,需要包含服務端業(yè)務邏輯執(zhí)行的時間
誤區(qū)5:認為超時時間越長任務接口成功率就越高,將讀取超時參數(shù)配置得太長
分析:讀取超時設置太長雾消,可能導致上游服務被下游拖垮灾搏,應該根據(jù)SLA設置合適的讀取超時挫望。有些時候,快速失敗或熔斷不是一件壞事兒
Spring Cloud Feign和 Ribbon配合使用狂窑,設置超時的三個坑
坑1
原因:默認情況下Feign的讀取超時是1秒,這個時間過于短了
解決︰根據(jù)自己的需要設置長一點
坑2
原因:如果要配置Feign的讀取超時媳板,就必須同時配置連接超時,才能生效
解決:同時配置readTimeout和connectTimeout
坑3
原因:Ribbon配置連接超時和讀取超時的參數(shù)大小寫和Feign略微不同
解決:Ribbon 使用ReadTimeout和 ConnectTimeout泉哈,注意大小寫
Spring Cloud Ribbon居然會自動重試我的接?
原因:Ribbon對于HTTP Get請求在一個服務器調用失敗后蛉幸,會自動到下一個節(jié)點重試一次
解決︰修改參數(shù),設置ribbon.MaxAutoRetriesNextServer=O;并且對于Get請求需要確保接冪等
小心Apache HttpClient對并發(fā)連接數(shù)的限制
原因:HttpClient對連接數(shù)有限制丛晦,默認一個域名2個并發(fā)奕纫,所有域名20個并發(fā)
解決:通過HttpClients.custom( ).setMaxConnPerRoute(10).setMaxConnTotal(20)來設置合適的值
數(shù)據(jù)庫事務
兩種錯誤寫法導致Spring聲明式事務未生效的兩個坑
原因:因為private方法無法代理,所以為private方法設置@Transactional注解無法生效事務
解決:除非使用AspectJ做靜態(tài)織入烫沙,否則需要確保只有public方法才設置@Transactional注解
原因:因為通過this自調用方法不走Spring的代理類,所以無法生效事務
解決︰確保事務性方法從外部通過代理類調用匹层。如果一定要從內(nèi)部調用,就要重新注入當前類調用
事務即便生效也不一定能回滾的兩個情況
情況1
原因:只有異常傳播出了標記了@Transactional注解的方法锌蓄,事務才能回滾
解決︰避免 catch住異常升筏,或者通過TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手動回滾
情況2
原因︰默認情況下,出現(xiàn) RuntimeException(非受檢異常)或Error的時候煤率,Spring 才會回滾事務
解決︰設置@Transactional(rollbackFor = Exception.class),來突破默認不回滾受檢異常的限制
不出異常仰冠,事務居然也不會提交?
原因:默認事務傳播策略是REQUIRED,子方法會復用當前事務,子方法出異常后回滾當前事務蝶糯,導致父方法也無法提交事務
解決︰設置REQUIRES_NEW方式的事務傳播策略洋只,讓子方法運行在獨立事務中
數(shù)據(jù)庫索引
聚簇索引的3個要點
要點1:B+樹,既是索引也是數(shù)據(jù)
要點2:自動創(chuàng)建,只能有一個
要點3:可以加速主鍵搜索和查詢
考慮二級索引的維護昼捍、空間和回表代價
原因
1识虚、維護:需要獨立維護—棵B+樹,用來加速數(shù)據(jù)查詢和排序;考慮數(shù)據(jù)頁的合并和分裂
2妒茬、空間:額外的存儲空間
3担锤、回表:二級索引不保存完整數(shù)據(jù),只保存索引鍵和主鍵字段
解決方案
1乍钻、按需創(chuàng)建
2肛循、考慮聯(lián)合索引
3、針對輕量級字段創(chuàng)建
建了索引但是用不上的3種情況
情況1:查詢數(shù)據(jù)內(nèi)容不走左匹配
情況2:查詢字段使用函數(shù)運算
情況3:查詢沒有使用聯(lián)合索引最左邊的列
多個獨立索引和聯(lián)合索引如何選擇的問題
考慮:多個字段會在一個條件中查詢银择,并且更有可能走索引覆蓋
不考慮:永遠只是單—列的查詢
相同的SQL語句多糠,有的時候能走索引有的時候不走索引的現(xiàn)象(MySQL optimizer trace)
原因:MySQL根據(jù)不同查詢方案的成本來決定是否走索引,索引過濾效果不好的時候浩考,可能走全表掃描更劃算
解決︰使用EXPLAIN來觀察查詢是否會走索引夹孔,開啟optimizertrace來了解具體原因和成本明細
判等問題
什么時候不能使用==進行值判等(-XX:+PrintStringTableStatistic)
基本類型之間
只能通過==判等
引用類型之間
結論:不能通過==判等,需要使用equals方法
原因:==是判斷指針相等、判斷對象實例是否是同一個搭伤,不代表對象內(nèi)容是否相同
為什么有的時候String使用==判等會奏效?
原因:直接使用雙引號聲明出來的兩個String 對象指向常量池中的相同字符串
工具:使用-XX:+PrintStringTableStatistic查看字符串常量表
包裝類型之間
Integer有的時候使用= =判等會奏效?
原因:Integer內(nèi)部其實做了緩存只怎,默認緩存[-128,127],在這個區(qū)間==奏效
實現(xiàn)equals方法可能出現(xiàn)的諸多坑
原因︰考慮性能先進行指針判等怜俐、需要對另一方進行判空身堡、確保類型相同的情況下再進行類型強制轉換
解決:使用IDE幫我們生成代碼
equals和 hashCode沒有配對實現(xiàn)的坑
原因:對象存入哈希表的行為不可測
解決∶務必配對實現(xiàn),使用IDE幫我們生成代碼
equals和compareTo實現(xiàn)邏輯不一致的坑
原因:ArrayList.indexOf使用equals判斷對象是否相等佑菩,而Collections.binarySearch使用compareTo方法來比較對象以實現(xiàn)搜索
解決:對于自定義的類型盾沫,如果要實現(xiàn)Comparable,記得equals殿漠、hashCode赴精、compareTo三者邏輯一致
Lombok @EqualsAndHashCode可能的兩個坑
坑1
原因:Lombok的@EqualsAndHashCode 注解實現(xiàn)equals和hashCode的時候,默認使用類的所有非static绞幌、非 transient的字段
解決:使用@EqualsAndHashCode.Exclude 排除一些字段
坑2
原因:Lombok的@EqualsAndHashCode注解實現(xiàn)equals和hashCode的時候蕾哟,默認不考慮父類
解決︰設置callSuper = true
數(shù)值計算
使用double進行浮點數(shù)運算的坑
原因:浮點數(shù)無法精確存儲,計算會損失精度
解決:
1、使用 BigDecimal表示和計算浮點數(shù),且務必使用字符串的構造方法來初始化 BigDecimal
2莲蜘、如果一定要用 Double來初始化 BigDecimal的話,可以使用BigDecimal valueOf方法
對double 或 float進行舍入格式化的坑
原因:使用 double或foat配合 String. format或DecimalFormat進行格式化舍入,也會有精度問題
解決:還是需要使用 BigDecima,配合 setscale進行舍入
使用equals對 BigDecimal進行判等的坑
原因:使用equals 比較BigDecimal 會同時比較value 和scale,1.0O不等于1谭确,和我們想的不一樣
解決︰如果只比較BigDecimal 的value,可以使用compareTo方法
把 BigDecimal 作為 Key加入HashSet的坑
原因:BigDecimal 的equals和 hashCode方法會同時考慮value 和scale,BigDecimal 的值1.0和1,雖然value相同但是scale 不同
解決︰使用TreeSet替換HashSet晴圾,或者先使用stripTrailingZeros方法去掉尾部的零
小心數(shù)值溢出但是沒有任何異常的坑
原因:大數(shù)值計算亚脆,數(shù)值可能默默溢出赊窥,沒有任何異常解決:
1、使用Math類的addExact、subtractExact 等 xxExact方法進行數(shù)值運算,在溢出時能拋出異常
2肠骆、使用大數(shù)類 BigInteger,需要轉到Long 時使用longValueExact塞耕,在溢出時能拋出異常
集合類
使用Arrays.asList 把數(shù)據(jù)轉換為List的3個坑
坑1:不能直接使用Arrays.asList來轉換基本類型數(shù)組
原因:只能是把 int裝箱為Integer蚀腿,不可能把int數(shù)組裝箱為Integer 數(shù)組,int數(shù)組整體作為了一個對象成為了泛型類型T解決:使用Arrays.stream 或傳入Integer[]
坑2: Arrays.asList返回的List不支持增刪操作
原因:Arrays.asList返回的List并不是我們期望的java.util.ArrayList,而是Arrays 的內(nèi)部類ArrayList
解決:重新new 一個ArrayList 初始化Arrays.asList返回的List
坑3∶對原始數(shù)組的修改會影響到我們獲得的那個List
原因︰內(nèi)部 ArrayList其實是直接使用了原始的數(shù)組
解決:重新new一個ArrayList初始化Arrays.asList返回的List
使用List.subList進行切片操作居然會導致OOM
原因:List.subList返回的是內(nèi)部類SubList 會引用原始List,SubList有強引用時會導致原來的 List也無法GC
解決∶
1扫外、不直接使用subList方法返回的SubList莉钙,而是使用newArrayList來重新構建一個普通的ArrayList
2、對于Java 8使用Stream的skip和limit API來跳過流中的元素筛谚,以及限制流中元素的個數(shù)
使用數(shù)據(jù)結構需要考慮平衡時間和空間(MAT)
時間:要實現(xiàn)快速查詢元素磁玉,可以考慮使用HashMap替換ArrayList
空間:但是HashMap 數(shù)據(jù)結構存儲利用率相比ArrayList會低很多
工具:使用MAT觀察數(shù)據(jù)結構內(nèi)存占用
LinkedList適合什么場景呢?
1、在各種常用場景下,LinkedList幾乎都不能在性能上勝出ArrayList
2刻获、使用任何數(shù)據(jù)結構最好根據(jù)自己的使用場景來評估和測試,以數(shù)據(jù)說話
空值處理
注意5種可能出現(xiàn)空指針的情況 (Arthas)
情況1
原因:參數(shù)值是Integer 等包裝類型,使用時因為自動拆箱出現(xiàn)了空指針異常
解決:對于Integer 的判空蝎毡,可以使用Optional.ofNullable來構造一個Optional厚柳,然后使用orElse(O)把 null替換為默認值再進行操作
情況2
原因:字符串比較出現(xiàn)空指針異常
解決:對于String和字面量的比較,可以把字面量放在前面沐兵,比如"OK".equals(s)
情況3
原因:諸如ConcurrentHashMap這樣的容器不支持Key和Value 為null别垮,強行put null的Key 或 Value 會出現(xiàn)空指針異常解決∶不要存null
情況4
原因:A對象包含了B,在通過A對象的字段獲得B之后扎谎,沒有對字段判空就級聯(lián)調用B的方法會出現(xiàn)空指針異常
解決︰使用Optional.ofNullable 配合map和ifPresent 方法
情況5
原因:方法或遠程服務返回的List不是空而是null碳想,沒有進行判空就直接調用List的方法,出現(xiàn)空指針異常
解決:使用Optional.ofNullable包裝一下返回值,然后通過.orElse(Collections.emptyList())毁靶,實現(xiàn)在List為 null的時候獲得一個空的List
工具
使用Arthas的 watch命令觀察方法入?yún)㈦时迹ㄎ籲ull參數(shù),使用stack命令觀察調用棧定位調用路徑
POJO字段設置默認值導致數(shù)據(jù)庫中的原始值被覆蓋的坑
原因:POJO中的字段有默認值,如果客戶端不傳值预吆,就會賦值為默認值龙填,導致每次都把默認值更新到了數(shù)據(jù)庫中
解決:POJO字段不要設置默認值,如果怕沒值可以讓數(shù)據(jù)庫設置默認值
你見過數(shù)據(jù)庫中出現(xiàn)null字符串的問題嗎?
原因:字符串格式化時拐叉,可能會把null值格式化為null字符串
解決:使用Optional的 orElse方法一鍵把空轉換為空字符串
客戶端不傳值以及傳null在POJO中都是null岩遗,如何區(qū)分?
原因:對于JSON到DTO的反序列化過程,null的表達是有歧義的,客戶端不傳某個屬性凤瘦,或者傳 null宿礁,這個屬性在DTO中都是null
解決︰使用Optional來包裝,以區(qū)分客戶端不傳數(shù)據(jù)還是故意傳null
MySQL中涉及NULL的3個容易忽略的坑
坑1
原因:MySQL中sum函數(shù)沒統(tǒng)計到任何記錄時蔬芥,會返回null而不是0
解決:可以使用IFNULL函數(shù)把NULL轉換為0
坑2
原因:MySQL中count字段不統(tǒng)計NULL值
解決:可以使用IFNULL函數(shù)把NULL轉換為О
坑3
原因:MySQL中使用諸如=梆靖、<、>這樣的算數(shù)比較操作符比較NULL的結果總是NULL坝茎,這種比較就顯得沒有任何意義
解決∶對NULL進行判斷只能使用IS NULL涤姊、IS NOT NULL或者ISNULL()函數(shù)
異常處理
————————————————
版權聲明:本文為CSDN博主「Apple_Web」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議嗤放,轉載請附上原文出處鏈接及本聲明思喊。
原文鏈接:https://blog.csdn.net/belongtocode/article/details/113684078