日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
在線求CR,你覺得我這段Java代碼還有優(yōu)化的空間嗎?

[[409316]]

10年積累的網(wǎng)站建設(shè)、做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先做網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有杞縣免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

 上周,因?yàn)橐獪y試一個(gè)方法的在并發(fā)場景下的結(jié)果是不是符合預(yù)期,我寫了一段單元測試的代碼。寫完之后截了個(gè)圖發(fā)了一個(gè)朋友圈,很多人表示短短的幾行代碼,涉及到好幾個(gè)知識點(diǎn)。

還有人給出了一些優(yōu)化的建議。那么,這是怎樣的一段代碼呢?涉及到哪些知識,又有哪些可以優(yōu)化的點(diǎn)呢?

讓我們來看一下。

背景

先說一下背景,也就是要知道我們單元測試要測的這個(gè)方法具體是什么樣的功能。我們要測試的服務(wù)是AssetService,被測試的方法是update方法。

update方法主要做兩件事,第一個(gè)是更新Asset、第二個(gè)是插入一條AssetStream。

更新Asset方法中,主要是更新數(shù)據(jù)庫中的Asset的信息,這里為了防止并發(fā),使用了樂觀鎖。

插入AssetStream方法中,主要是插入一條AssetStream的流水信息,為了防止并發(fā),這里在數(shù)據(jù)庫中增加了唯一性約束。

為了保證數(shù)據(jù)一致性,我們通過本地事務(wù)將這兩個(gè)操作包在同一個(gè)事務(wù)中。

以下是主要的代碼,當(dāng)然,這個(gè)方法中還會有一些前置的冪等性校驗(yàn)、參數(shù)合法性校驗(yàn)等,這里就都省略了: 

 
 
 
  1. @Service  
  2. public class AssetServiceImpl implements AssetService {  
  3.     @Autowired  
  4.     private TransactionTemplate transactionTemplate;  
  5.     @Override  
  6.     public String update(Asset asset) {  
  7.         //參數(shù)檢查、冪等校驗(yàn)、從數(shù)據(jù)庫取出最新asset等。  
  8.         return transactionTemplate.execute(status -> {  
  9.             updateAsset(asset);  
  10.             return insertAssetStream(asset);  
  11.         });  
  12.     }  

因?yàn)檫@個(gè)方法可能會在并發(fā)場景中執(zhí)行,所以該方法通過事務(wù)+樂觀鎖+唯一性約束做了并發(fā)控制。關(guān)于這部分的細(xì)節(jié)就不多講了,大家感興趣的話后面我再展開關(guān)于如何防并發(fā)的內(nèi)容。

單測

因?yàn)樯厦孢@個(gè)方法是可能在并發(fā)場景中被調(diào)用的,所以需要在單測中模擬并發(fā)場景,于是,我就寫了以下的單元測試的代碼: 

 
 
 
  1. public class AssetServiceImplTest {  
  2.     private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()  
  3.         .setNameFormat("demo-pool-%d").build();  
  4.     private static ExecutorService pool = new ThreadPoolExecutor(20, 100,  
  5.         0L, TimeUnit.MILLISECONDS,  
  6.         new LinkedBlockingQueue(128), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());  
  7.     @Autowired  
  8.     private AssetService assetService;  
  9.     @Test 
  10.      public void test_updateConcurrent() {  
  11.         Asset asset = getAsset();  
  12.         //參數(shù)的準(zhǔn)備  
  13.         //... 
  14.          //并發(fā)場景模擬  
  15.         CountDownLatch countDownLatch = new CountDownLatch(10);  
  16.         AtomicInteger atomicInteger =new AtomicInteger();          
  17.          //并發(fā)批量修改,只有一條可以修改成功  
  18.         for (int i = 0; i < 10; i++) {  
  19.             pool.execute(() -> {  
  20.                 try {  
  21.                     String streamNo = assetService.update(asset);  
  22.                 } catch (Exception e) { 
  23.                      System.out.println("Error : " + e);  
  24.                     failedCount.getAndIncrement();  
  25.                 } finally {  
  26.                     countDownLatch.countDown();  
  27.                 }  
  28.             });  
  29.         }  
  30.         try {  
  31.             //主線程等子線程都執(zhí)行完之后查詢最新的資產(chǎn)  
  32.             countDownLatch.await();  
  33.         } catch (InterruptedException e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.         Assert.assertEquals(failedCount.intValue(), 9);  
  37.         // 從數(shù)據(jù)庫中反查出最新的Asset  
  38.         // 再對關(guān)鍵字段做注意校驗(yàn)  
  39.     }  

以上,就是我做了簡化之后的單元測試的部分代碼。因?yàn)橐獪y并發(fā)場景,所以這里面涉及到了很多并發(fā)相關(guān)的知識。

很多人之前和我說,并發(fā)相關(guān)的知識自己了解的很多,但是好像沒什么機(jī)會寫并發(fā)的代碼。其實(shí),單元測試就是個(gè)很好的機(jī)會。

我們來看看上面的代碼涉及到哪些知識點(diǎn)?

知識點(diǎn)

以上這段單元測試的代碼中涉及到幾個(gè)知識點(diǎn),我這里簡單說一下。

線程池

這里面因?yàn)橐M并發(fā)的場景,所以需要用到多線程, 所以我這里使用了線程池,而且我沒有直接用Java提供的Executors類創(chuàng)建線程池。

而是使用guava提供的ThreadFactoryBuilder來創(chuàng)建線程池,使用這種方式創(chuàng)建線程時(shí),不僅可以避免OOM的問題,還可以自定義線程名稱,更加方便的出錯(cuò)的時(shí)候溯源。(關(guān)于線程池創(chuàng)建的OOM問題)

CountDownLatch

因?yàn)槲业膯卧獪y試代碼中,希望在所有的子線程都執(zhí)行之后,主線程再去檢查執(zhí)行結(jié)果。

所以,如何使主線程阻塞,直到所有子線程執(zhí)行完呢?這里面用到了一個(gè)同步輔助類CountDownLatch。

用給定的計(jì)數(shù)初始化 CountDownLatch。由于調(diào)用了 countDown() 方法,所以在當(dāng)前計(jì)數(shù)到達(dá)零之前,await 方法會一直受阻塞。

AtomicInteger

因?yàn)槲以趩螠y代碼中,創(chuàng)建了10個(gè)線程,但是我需要保證只有一個(gè)線程可以執(zhí)行成功。所以,我需要對失敗的次數(shù)做統(tǒng)計(jì)。

那么,如何在并發(fā)場景中做計(jì)數(shù)統(tǒng)計(jì)呢,這里用到了AtomicInteger,這是一個(gè)原子操作類,可以提供線程安全的操作方法。

異常處理

因?yàn)槲覀兡M了多個(gè)線程并發(fā)執(zhí)行,那么就一定會存在部分線程執(zhí)行失敗的情況。

因?yàn)榉椒ǖ讓記]有對異常進(jìn)行捕獲。所以需要在單測代碼中進(jìn)行異常的捕獲。 

 
 
 
  1. try {  
  2.       String streamNo = assetService.update(asset);  
  3.   } catch (Exception e) {  
  4.       System.out.println("Error : " + e);  
  5.       failedCount.increment();  
  6.   } finally {  
  7.       countDownLatch.countDown();  
  8.   } 

這段代碼中,try、catch、finall都用上了,而且位置是不能調(diào)換的。失敗次數(shù)的統(tǒng)計(jì)一定要放到catch中,countDownLatch的countDown也一定要放到finally中。

Assert

這個(gè)相信大家都比較熟悉,這就是JUnit中提供的斷言工具類,在單元測試時(shí)可以用做斷言。這就不詳細(xì)介紹了。

優(yōu)化點(diǎn)

以上代碼涉及到了很多知識點(diǎn),但是,難道就沒有什么優(yōu)化點(diǎn)了嗎?

首先說一下,其實(shí)單元測試的代碼對性能、穩(wěn)定性之類的要求并不高,所謂的優(yōu)化點(diǎn),也并不是必要的。這里只是說討論下,如果真的是要做到精益求精,還有什么點(diǎn)可以優(yōu)化呢?

使用LongAdder代替AtomicInteger

我的朋友圈的網(wǎng)友@zkx 提出,可以使用LongAdder代替AtomicInteger。

java.util.concurrency.atomic.LongAdder是Java8新增的一個(gè)類,提供了原子累計(jì)值的方法。而且在其Javadoc中也明確指出其性能要優(yōu)于AtomicLong。

首先它有一個(gè)基礎(chǔ)的值base,在發(fā)生競爭的情況下,會有一個(gè)Cell數(shù)組用于將不同線程的操作離散到不同的節(jié)點(diǎn)上去(會根據(jù)需要擴(kuò)容,最大為CPU核數(shù),即最大同時(shí)執(zhí)行線程數(shù)),sum()會將所有Cell數(shù)組中的value和base累加作為返回值。

核心的思想就是將AtomicLong一個(gè)value的更新壓力分散到多個(gè)value中去,從而降低更新熱點(diǎn)。所以在激烈的鎖競爭場景下,LongAdder性能更好。

增加并發(fā)競爭

朋友圈網(wǎng)友 @Cafebabe 和 @普渡眾生的面癱青年 以及 @嘉俊 ,都提到同一個(gè)優(yōu)化點(diǎn),那就是如何增加并發(fā)競爭。

這個(gè)問題其實(shí)我在發(fā)朋友圈之前就有想到過,心中早已經(jīng)有了答案,只不過有多位朋友能夠幾乎同時(shí)提到這一點(diǎn)還是很不錯(cuò)的。

我們來說說問題是什么。

我們?yōu)榱颂嵘l(fā),使用線程池創(chuàng)建了多個(gè)線程,想讓多個(gè)線程并發(fā)執(zhí)行被測試的方法。

但是,我們是在for循環(huán)中依次執(zhí)行的,那么理論上這10次update方法的調(diào)用是順序執(zhí)行的。

當(dāng)然,因?yàn)橛蠧PU時(shí)間片的存在,這10個(gè)線程會爭搶CPU,真正執(zhí)行的過程中還是會發(fā)生并發(fā)沖突的。

但是,為了穩(wěn)妥起見,我們還是需要盡量模擬出多個(gè)線程同時(shí)發(fā)起方法調(diào)用的。

優(yōu)化的方法也比較簡單,那就是在每一個(gè)update方法被調(diào)用之前都wait一下,直到所有的子線程都創(chuàng)建成功了,再開始一起執(zhí)行。

這里就可以用到CyclicBarrier來實(shí)現(xiàn),CyclicBarrier和CountDownLatch一樣,都是關(guān)于線程的計(jì)數(shù)器。

CountDownLatch: 一個(gè)線程(或者多個(gè)), 等待另外N個(gè)線程完成某個(gè)事情之后才能執(zhí)行。?

CyclicBrrier: N個(gè)線程相互等待,任何一個(gè)線程完成之前,所有的線程都必須等待。

所以,最終優(yōu)化后的單測代碼如下: 

 
 
 
  1. //主線程根據(jù)此CountDownLatch阻塞  
  2. CountDownLatch mainThreadHolder = new CountDownLatch(10);  
  3. //并發(fā)的多個(gè)子線程根據(jù)此CyclicBarrier阻塞  
  4. CyclicBarrier cyclicBarrier = new CyclicBarrier(10);  
  5. //失敗次數(shù)計(jì)數(shù)器  
  6. LongAdder failedCount = new LongAdder();  
  7. //并發(fā)批量修改,只有一條可以修改成功  
  8. for (int i = 0; i < 10; i++) {  
  9.     pool.execute(() -> {  
  10.         try {  
  11.             //子線程等待,所有線程就緒后開始執(zhí)行  
  12.             cyclicBarrier.await();  
  13.             //調(diào)用被測試的方法  
  14.             String streamNo = assetService.update(asset);  
  15.         } catch (Exception e) {  
  16.             //異常發(fā)生時(shí),對失敗計(jì)數(shù)器+1  
  17.             System.out.println("Error : " + e);  
  18.             failedCount.increment();  
  19.         } finally {  
  20.             //主線程的阻塞器奇數(shù)-1  
  21.             mainThreadHolder.countDown();  
  22.         }  
  23.     });  
  24. }  
  25. try {  
  26.     //主線程等子線程都執(zhí)行完之后查詢最新的資產(chǎn)池計(jì)劃  
  27.     mainThreadHolder.await();  
  28. } catch (InterruptedException e) {  
  29.     e.printStackTrace();  
  30. }  
  31. //斷言,保證失敗9次,則成功一次  
  32. Assert.assertEquals(failedCount.intValue(), 9); 
  33. // 從數(shù)據(jù)庫中反查出最新的Asset  
  34. // 再對關(guān)鍵字段做注意校驗(yàn) 

以上,就是關(guān)于我的一次單元測試的代碼所涉及到的知識點(diǎn),以及目前所能想到的相關(guān)的優(yōu)化點(diǎn)。 

 


當(dāng)前名稱:在線求CR,你覺得我這段Java代碼還有優(yōu)化的空間嗎?
文章起源:http://m.5511xx.com/article/dhpgjhe.html