JUC包中常用類的解析)
前言hello hello這里是洋不寫bug~歡迎大家點(diǎn)贊關(guān)注收藏這篇是線程部分最后一篇博客會詳細(xì)的解析JUC包中常用的工具類JUC包就是java.util.concurrent包是 Java 官方給多線程高并發(fā)開發(fā)提供的一整套「并發(fā)編程專用工具與底層組件庫」解決多線程下的線程安全、資源競爭、線程協(xié)作、線程復(fù)用等所有痛點(diǎn)。個(gè)人主頁洋不寫bug的博客所屬專欄JavaEE學(xué)習(xí)鐵汁們對于JavaEE的各種常用核心語法都可以在上面的前端專欄學(xué)習(xí)專欄正在持續(xù)更新中有問題可以寫在評論區(qū)或者私信我哦~1Callable接口Callable接口其實(shí)跟Runnable接口非常類似Runnable接口的run方法是void類型run方法里面寫上線程要執(zhí)行的任務(wù)沒有返回值而Callable接口的call方法可以指定返回值例如一個(gè)線程計(jì)算1 2 3 … 100如果使用Runnable接口的話是不能直接打印result的需要再定義一個(gè)變量傳遞一下這樣就比較麻煩如下publicclassDemo34{privatestaticintsum0;publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadtnewThread(()-{intresult0;for(inti1;i100;i){resulti;}sumresult;});t.start();t.join();System.out.println(sum);}}使用Callable的話用匿名內(nèi)部類的方式創(chuàng)建一個(gè)callable對象需要指定泛型參數(shù)泛型參數(shù)就是call方法的返回值的類型接著重寫call方法在call方法中返回resultnew Thread(參數(shù))這里只接收Runnabl類型的對象不接收Callable類型的所以就需要有個(gè)中間的橋梁來轉(zhuǎn)化一下這個(gè)Futuretask類實(shí)現(xiàn)的就是Runnable接口就能作為中間的橋梁讓Thread來執(zhí)行call方法代碼如下所示importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.FutureTask;publicclassDemo35{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{CallableIntegercallablenewCallableInteger(){OverridepublicIntegercall()throwsException{intresult0;for(inti1;i100;i){resulti;}returnresult;}};FutureTaskIntegerfutureTasknewFutureTask(callable);ThreadtnewThread(futureTask);t.start();System.out.println(futureTask.get());}}這個(gè)futureTask的get()方法帶有阻塞會等到call方法執(zhí)行完成后再打印2ReentrantLock可重入鎖reentrant的翻譯就是可重入的ReentrantLock是一把比較傳統(tǒng)的鎖在synchronized還不成熟的時(shí)候這個(gè)鎖就是進(jìn)行多線程加鎖的主要方案sychronized使用時(shí)不需要手動(dòng)釋放鎖ReentrantLock使用時(shí)需要手動(dòng)釋放鎖雖然這樣更靈活了但是也容易遺漏unlock代碼如下所示這樣看起來就感覺不太美觀importjava.util.concurrent.locks.ReentrantLock;publicclassDemo36{privatestaticintcount0;publicstaticvoidmain(String[]args)throwsInterruptedException{ReentrantLocklockernewReentrantLock();Threadt1newThread(()-{for(inti0;i50000;i){try{locker.lock();count;}finally{locker.unlock();}}});Threadt2newThread(()-{for(inti0;i50000;i){try{locker.lock();count;}finally{locker.unlock();}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}}sychronized在申請鎖失敗的時(shí)候就會死等而ReentrantLock就可以通過trylock的方式等待一段時(shí)間就放棄ReentrantLock對象有個(gè)tryLock方法這個(gè)方法會嘗試加鎖如果加上了就返回true沒加上就返回false例如嘗試加鎖三次要是加不上就放棄代碼如下所示Threadt1newThread(()-{for(inti0;i50000;i){intn0;while(n3){if(locker.tryLock()){try{count;}finally{locker.unlock();}}else{n;}}}});就好比在ATM機(jī)上取錢嘗試加鎖如果加了一會還沒加上就直接彈出一個(gè)窗口告訴用戶扣款失敗另外sychronized是非公平鎖ReentrantLock是默認(rèn)是非公平鎖但可以通過構(gòu)造方法傳入一個(gè)true開啟公平模式也就是按照線程先來后到的順序加鎖ReentrantLocklockernewReentrantLock(true);3信號量SemaphoreSemphore類本質(zhì)上就是一個(gè)計(jì)數(shù)器描述“可用資源”的個(gè)數(shù)申請資源P操作使計(jì)數(shù)器 1釋放資源V操作使計(jì)數(shù)器 - 1如果計(jì)數(shù)器為0了再去申請資源就會觸發(fā)阻塞就類似停車場門口的停車位電子牌顯示當(dāng)前有N個(gè)空閑車位開進(jìn)去一輛車N 1開走一輛車N - 1當(dāng)N等于0時(shí)這時(shí)候新來的車就進(jìn)不去了這時(shí)候有兩種選擇要么在門口等有車出來再進(jìn)去阻塞等待要么尋找新的停車場在創(chuàng)建semaphore對象時(shí)先指定資源的數(shù)量acquire方法是申請資源可能會造成阻塞因此需要拋出異常realease方法是釋放資源如下所示importjava.util.concurrent.Semaphore;publicclassDemo37{publicstaticvoidmain(String[]args)throwsInterruptedException{SemaphoresemaphorenewSemaphore(3);semaphore.acquire();semaphore.release();}}這里有的鐵汁可能會比較奇怪那就是為什么申請資源叫P操作釋放資源叫V操作呢這跟英文單詞也對不上呀這是因?yàn)樾盘柫渴堑辖芩固乩岢龅母拍钸@個(gè)大佬是荷蘭人荷蘭語中申請資源和釋放資源的首字母就是P和V如果資源數(shù)為0還嘗試獲取資源就會進(jìn)入阻塞狀態(tài)代碼如下importjava.util.concurrent.Semaphore;publicclassDemo37{publicstaticvoidmain(String[]args)throwsInterruptedException{SemaphoresemaphorenewSemaphore(3);semaphore.acquire();System.out.println(執(zhí)行P操作);semaphore.acquire();System.out.println(執(zhí)行P操作);semaphore.acquire();System.out.println(執(zhí)行P操作);semaphore.acquire();System.out.println(執(zhí)行P操作);}}也可以使用信號量來模擬加鎖解決線程計(jì)數(shù)時(shí)的安全問題代碼如下importjava.util.concurrent.Semaphore;publicclassDemo38{privatestaticintcount0;publicstaticvoidmain(String[]args)throwsInterruptedException{SemaphoresemaphorenewSemaphore(1);Threadt1newThread(()-{for(inti0;i50000;i){try{semaphore.acquire();count;semaphore.release();}catch(InterruptedExceptione){e.printStackTrace();}}});Threadt2newThread(()-{for(inti0;i50000;i){try{semaphore.acquire();count;semaphore.release();}catch(InterruptedExceptione){e.printStackTrace();}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}}一般更多用的是原子類這個(gè)用的不多鐵汁們了解下即可4CountDownLatch這個(gè)單詞的翻譯有鎖存器閉鎖但是翻譯有點(diǎn)抽象所以這里鐵汁們只看英文即可在實(shí)際開發(fā)中有時(shí)需要把大任務(wù)拆分為很多的小任務(wù)來執(zhí)行例如大文件的下載通過多線程來執(zhí)行要確認(rèn)這些小任務(wù)都執(zhí)行完了才能進(jìn)入到下個(gè)階段這就用到了CountDownLatch代碼如下所示這里創(chuàng)建8個(gè)線程每個(gè)線程中執(zhí)行阻塞1s的邏輯創(chuàng)建CountDownLatch對象時(shí)傳入的參數(shù)表示需要記錄的線程數(shù)在寫線程執(zhí)行的任務(wù)時(shí)最后用countDownLatch調(diào)用一下countDown方法這就表示這個(gè)線程的任務(wù)執(zhí)行完了讓countDownLatch記錄一下接著在主線程中調(diào)用countDownLatch的await方法這就會阻塞主線程等到8個(gè)線程線程的任務(wù)都完成了主線程再往下執(zhí)行importjava.util.concurrent.CountDownLatch;publicclassDemo39{publicstaticvoidmain(String[]args)throwsInterruptedException{CountDownLatchcountDownLatchnewCountDownLatch(8);for(inti0;i8;i){intidi;ThreadtnewThread(()-{try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(線程id執(zhí)行完畢);countDownLatch.countDown();});t.start();}countDownLatch.await();System.out.println(執(zhí)行下一階段的任務(wù));}}5ConcurrentHashMap在多線程環(huán)境下使用哈希表如果使用HashMap那線程就是不安全的而HashTable在重要方法上都加了鎖但是效率非常低現(xiàn)在基本上已經(jīng)被淘汰了如果想要線程安全的話就使用ConcurrentHashMap那ConcurrentHashMap和HashTable同樣都是加鎖保持線程安全有什么區(qū)別呢ConcurrentHashMap最大的調(diào)整是針對鎖的粒度上的優(yōu)化哈希表是把key映射到數(shù)組下標(biāo)上的哈希表的每個(gè)數(shù)組下標(biāo)是一個(gè)鏈表如下圖但是對于HashTable來說針對this加鎖任意一個(gè)線程只要操作這個(gè)hash表就會觸發(fā)鎖競爭即使兩個(gè)線程操作的是不同的鏈表因此HashTable的效率就很低而ConcurrentHashMap則對每一個(gè)數(shù)組下標(biāo)都加上了鎖只有多個(gè)線程同時(shí)修改一個(gè)鏈表時(shí)才會產(chǎn)生阻塞在哈希表中每個(gè)數(shù)組下標(biāo)叫做桶因此ConcurrentHashMap這種加鎖也叫做“鎖桶”實(shí)踐中哈希表中桶的個(gè)數(shù)是非常多的針對哈希元素的修改大概率是在不同的桶上的因此觸發(fā)鎖競爭的概率是非常小的甚至可以忽略不計(jì)這里可能有的鐵汁就會想這里的效率是提升了但是加了這么多鎖是不是會有很多額外的空間開銷呀?在Java中任意對象都可以作為鎖對象這里鎖對象就不需要再去創(chuàng)建直接拿每個(gè)鏈表的頭節(jié)點(diǎn)當(dāng)鎖對象即可ConcurrentHashMap在擴(kuò)容操作上也進(jìn)行了優(yōu)化所謂的擴(kuò)容操作就是搞一個(gè)更大的數(shù)組把原數(shù)組中的元素hash到新數(shù)組上在這個(gè)搬運(yùn)過程中為了安全是要加鎖的像HashMap就是規(guī)定在搬運(yùn)過程中其他線程不能修改元素就會影響效率而ConcurrentHashMap在擴(kuò)容的時(shí)候不會一下子把鍵值對都搬完而是每次都搬一點(diǎn)點(diǎn)單詞搬運(yùn)的時(shí)間是非常短的這樣隨時(shí)都可以快速的讀取數(shù)據(jù)修改數(shù)據(jù)ConcurrentHashMap在數(shù)組擴(kuò)容搬遷時(shí)會有下面三種情況還沒搬遷的桶 → 直接修改舊哈希表正在搬遷的桶 → 加鎖阻塞等搬完后改新哈希表因?yàn)榘岬蒙偎俣瓤爝@個(gè)加鎖阻塞的時(shí)間是很短的已經(jīng)搬遷完的桶 → 直接修改新哈希表結(jié)語這篇博客是JavaEE線程部分的最后一篇博客5種創(chuàng)建線程的方法已經(jīng)解析完了繼承Thread實(shí)現(xiàn)Runnable基于lambda(本質(zhì)上還是Runnable)實(shí)現(xiàn)Callable基于線程池這個(gè)在面試中是高頻題問創(chuàng)建線程的方式有哪些很多鐵汁經(jīng)常會遺漏掉后兩個(gè)另外HashMapHashTableConcurrentHashMap之間的區(qū)別也是面試中非常高頻的一個(gè)題鐵汁們就可以從線程安全加鎖優(yōu)化上來回答以上就是今天的所有內(nèi)容啦完結(jié)撒花