Java多線程之JUC(java.util.concurrent)的常見(jiàn)類(多線程編程常用類)
Callable接口
這個(gè)東西可以類比于之前見(jiàn)過(guò)的Runnable接口.兩者的區(qū)別在于Runnable關(guān)注執(zhí)行過(guò)程,不關(guān)注執(zhí)行結(jié)果.Callable關(guān)注執(zhí)行結(jié)果,它之中的call方法(類比于run方法)返回值就是線程執(zhí)行任務(wù)的結(jié)果.Callable<V>里面的V期望線程的入口方法里,返回值是啥類型,此處的泛型參數(shù)就是啥類型.
Callable優(yōu)勢(shì)
示例:創(chuàng)建線程計(jì)算1+2+...+1000,使用Runnable版本
public class ThreadDemo7 { private static int sum = 0; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { @Override public void run() { int result = 0; for(int i = 0; i <= 1000; i++) { result += i; } sum = result; } }); t.start(); t.join(); //主線程獲取到計(jì)算結(jié)果 //此處要想獲取到結(jié)果,就需要專門搞一個(gè)成員變量保存上述的計(jì)算結(jié)果 System.out.println("sum =" + sum); } }
這么做雖然能夠解決問(wèn)題,但是代碼不是很優(yōu)雅,這時(shí)我們就希望依靠返回值來(lái)直接保存計(jì)算結(jié)果,
這就用到了Callable接口,使用流程如下:
1.創(chuàng)建一個(gè)匿名內(nèi)部類,實(shí)現(xiàn)Callable接口.Callable帶有泛型參數(shù).泛型參數(shù)表示返回值的類型
2.重寫Callable的call方法,完成累加的過(guò)程,直接通過(guò)返回值返回計(jì)算結(jié)果
3.把callable示例用FutureTask包裝一下.
4.創(chuàng)建線程,線程的構(gòu)造方法傳入FutureTask.此時(shí)新線程就會(huì)執(zhí)行FutureTask內(nèi)部的Callable的call方法,完成計(jì)算.計(jì)算結(jié)果就放進(jìn)了FutureTask對(duì)象中.
5.在主線程中調(diào)用futureTask.get()能夠阻塞等待新線程計(jì)算完畢.并獲取FutureTask中的結(jié)果.
代碼如下:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo8 { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int result = 0; for(int i = 0; i <= 1000; i++) { result += i; } return result; } }; FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread t = new Thread(futureTask); t.start(); //接下來(lái)這個(gè)代碼也不需要join,使用futureTask獲取到結(jié)果. //get()方法具有阻塞功能.如果線程不執(zhí)行完畢,get就會(huì)阻塞 //等到線程執(zhí)行完了,return的結(jié)果,就會(huì)被get返回回來(lái) System.out.println(futureTask.get()); } }
可以看到,使用Callable和FutureTask之后,代碼簡(jiǎn)化了很多,也不必手動(dòng)寫線程同步代碼了.
理解Callable
Callable通常需要搭配FutureTask來(lái)使用.FutureTask用來(lái)保存Callable的返回結(jié)果.因?yàn)镃allable往往是在另一個(gè)線程中執(zhí)行的,什么時(shí)候執(zhí)行完并不確定.
FutureTask就可以負(fù)責(zé)這個(gè)等待結(jié)果出來(lái)的工作.
理解FutureTask
FutureTask即未來(lái)的任務(wù),既然這個(gè)任務(wù)是在未來(lái)執(zhí)行完畢,最終取結(jié)果時(shí)就需要一張憑證.
可以想象成去吃麻辣燙.當(dāng)餐點(diǎn)好后,后廚就開始做了.同時(shí)前臺(tái)會(huì)給你一張小票.這個(gè)小票就是FutureTask.后面我們可以隨時(shí)憑這張小票去查看自己的這份麻辣燙做出來(lái)沒(méi).
總結(jié):創(chuàng)建線程的方式:1.繼承Thread(包含匿名內(nèi)部類).2.實(shí)現(xiàn)Runnable(包含匿名內(nèi)部類).
3.基于lambda. 4.基于Callable. 5.基于線程池.
ReentrantLock
可重入互斥鎖.和synchronized定位類似,都是用來(lái)實(shí)現(xiàn)互斥效果,保證線程安全.
ReentrantLock也是可重入鎖."Reentrant"這個(gè)單詞的原意就是"可重入".
ReentrantLock的用法:
lock():加鎖,如果獲取不到鎖就死等.
trylock(超時(shí)時(shí)間):加鎖,如果獲取不到鎖,等待一定時(shí)間之后就放棄加鎖.(此處通過(guò)trylock提供了更多的可操作空間)
unlock():解鎖
ReentrantLock lock = new ReentrantLock(); ----------------------------------------- lock.lock(); try { //working... } finally { lock.unlock() }
ReentrantLock和synchronized的區(qū)別
通過(guò)上述解釋,我們不免發(fā)現(xiàn)ReentrantLock和Synchronized非常相像,下面來(lái)說(shuō)一說(shuō)他們的區(qū)別:
1.synchronized是一個(gè)關(guān)鍵字,是JVM內(nèi)部實(shí)現(xiàn)的(大概率是基于C++實(shí)現(xiàn)).ReentrantLock是標(biāo)準(zhǔn)庫(kù)中的一個(gè)類,在JVM外實(shí)現(xiàn)的(基于Java實(shí)現(xiàn)).
2.synchronized使用時(shí)不需要手動(dòng)釋放鎖.ReentrantLock使用時(shí)需要手動(dòng)釋放.使用起來(lái)更靈活,但是也容易遺漏unlock.
3.synchronized在申請(qǐng)失敗時(shí),會(huì)死等.ReentrantLock可以通過(guò)trylock的方式等待一段時(shí)間后就放棄
4.synchronized是非公平鎖,ReentrantLock默認(rèn)是非公平鎖.可以通過(guò)一個(gè)構(gòu)造方法傳入一個(gè)true進(jìn)入公平鎖模式(原理:通過(guò)隊(duì)列記錄加鎖線程的先后順序).
//ReentrantLock的構(gòu)造方法 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }5.搭配的等待通知機(jī)制是不同的
對(duì)于synchronize,搭配wait/notify
對(duì)于ReentrantLock,搭配Condition類,功能比wait,notify略強(qiáng)一些.
如何選擇使用哪個(gè)鎖
1.鎖競(jìng)爭(zhēng)不激烈時(shí),使用synchronized,效率更高,自動(dòng)釋放更方便.
2.鎖競(jìng)爭(zhēng)激烈時(shí),搭配trylock更靈活控制鎖的行為,而不是死等
3.如果需要使用公平鎖,使用ReentrantLock.
其實(shí),一般情況下會(huì)使用synchronized即可.
信號(hào)量Semaphore
信號(hào)量,用來(lái)表示"可用資源的個(gè)數(shù)".本質(zhì)上就是一個(gè)計(jì)數(shù)器.
理解信號(hào)量(想象成一個(gè)更廣義的鎖)
可以把信號(hào)量想象成是停車場(chǎng)的展示牌:當(dāng)前有車位100個(gè).表示有100個(gè)可用資源.
當(dāng)有車開進(jìn)去的時(shí)候,就相當(dāng)于申請(qǐng)(acquire)一個(gè)可用資源,可用車位就-1.(這稱為信號(hào)量的P操作)
當(dāng)有車開出來(lái)的時(shí)候,就相當(dāng)于釋放(release)一個(gè)可用資源,可用車位就+1.(這稱為信號(hào)量的V操作)
如果計(jì)數(shù)器的值已經(jīng)為0了,還嘗試申請(qǐng)資源,就會(huì)堵塞等待,直到有其它線程釋放資源.
Semaphore的PV操作中的加減計(jì)數(shù)器操作都是原子的,可以在多線程下直接使用.
所謂鎖本質(zhì)也是一種特殊的信號(hào)量.鎖可以認(rèn)為就是計(jì)數(shù)值為1的信號(hào)量,釋放狀態(tài)就是1,加鎖狀態(tài)就是0.對(duì)于這種非0即1的信號(hào)量.稱為"二元信號(hào)量".
代碼示例:
1.創(chuàng)建Semaphore示例,初始化為4,表示有4個(gè)可用資源.
2.acquire方法表示申請(qǐng)資源(P操作),release方法表示釋放資源(V操作).
3.創(chuàng)建20個(gè)線程,每個(gè)線程都嘗試申請(qǐng)資源,sleep一秒之后,釋放資源,觀察程序的執(zhí)行效果.
import java.util.concurrent.Semaphore; public class ThreadDemo9 { public static void main(String[] args) { Semaphore semaphore = new Semaphore(4); Runnable runnable = new Runnable() { @Override public void run() { try { System.out.println("申請(qǐng)資源"); semaphore.acquire(); System.out.println("我獲取到資源了"); Thread.sleep(1000); System.out.println("我釋放資源了"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; for(int i = 0; i < 10; i++) { Thread t = new Thread(runnable); t.start(); } } }
總結(jié):如何保證線程安全
1.synchronized
2.Reentrantlock
3.CAS(原子類)
4.Semaphore (也可以用于實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型:
定義兩個(gè)信號(hào)量:一個(gè)用來(lái)表示隊(duì)列中有多少個(gè)可以消費(fèi)的元素sem1,另一個(gè)用于表示隊(duì)列中有多少個(gè)可放置新元素的空間sem2.
生產(chǎn):sem1.V(),sem2.P()
消費(fèi):sem1.P(),sem2.V()
CountDownLatch
同時(shí)等待N個(gè)任務(wù)執(zhí)行結(jié)束.(多線程中執(zhí)行一個(gè)任務(wù),把大的任務(wù)分為幾個(gè)部分,由每個(gè)線程分別執(zhí)行).
就好像跑步比賽,10個(gè)選手依次就位,哨聲響了才能同時(shí)出發(fā);所有選手都通過(guò)終點(diǎn),才能公布成績(jī).
1.構(gòu)造CountDownLatch實(shí)例,初始化10表示有10個(gè)任務(wù)需要完成.
2.每個(gè)任務(wù)執(zhí)行完畢,都調(diào)用latch.countDown().在CountDownLatch1內(nèi)部的計(jì)數(shù)器同時(shí)自減
3.主線程中使用latch.await();阻塞等待所有任務(wù)執(zhí)行完畢.相當(dāng)于計(jì)數(shù)器為0了.
import java.util.Random; import java.util.concurrent.CountDownLatch; public class ThreadDemo10 { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(10); Runnable r = new Runnable() { @Override public void run() { Random random = new Random(); int x = random.nextInt(5) + 1; try { Thread.sleep(x * 1000); latch.countDown(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; for(int i = 0; i < 10; i++) { new Thread(r).start(); } //必須等到所有線程全部結(jié)束 latch.await(); System.out.println("比賽結(jié)束"); } }
相關(guān)面試題
1.線程同步的方式有哪些?
synchronized, ReentrantLock, Semaphore等都用于線程同步.
2.為什么有了synchronized還需要juc下的lock?
以juc的ReentrantLock為例,
synchronized使用時(shí)不需要手動(dòng)釋放鎖.ReentrantLock使用時(shí)需要通過(guò)手動(dòng)釋放,使用起來(lái)更加靈活.
synchronized在申請(qǐng)失敗后會(huì)死等.ReentrantLock可以通過(guò)trylock的方式等待一段時(shí)間就放棄.
synchronized是非公平鎖,ReentrantLock默認(rèn)是非公平鎖.可以通過(guò)構(gòu)造方法傳入一個(gè)true開啟公平鎖模式
synchronized是通過(guò)Object的wait/notify實(shí)現(xiàn)等待-喚醒.每次喚醒的是一個(gè)隨機(jī)等待的線程.ReentrantLock搭配Condition類實(shí)現(xiàn)等待-喚醒,可以更精確的控制喚醒某個(gè)指定的線程.
3.信號(hào)量聽說(shuō)過(guò)嗎?都用于哪些場(chǎng)景下?
信號(hào)量,用來(lái)表示"可用資源的個(gè)數(shù)",本質(zhì)上就是一個(gè)計(jì)數(shù)器.
使用信號(hào)量可以實(shí)現(xiàn)"共享鎖",比如某個(gè)資源允許3個(gè)線程同時(shí)使用,那么就可以使用P操作加鎖,V操作為解鎖,前三個(gè)線程的P操作都能順利返回,后續(xù)再進(jìn)行P操作就會(huì)阻塞等待,直到前面的線程執(zhí)行了V操作.
總結(jié)
到此這篇關(guān)于Java多線程之JUC(java.util.concurrent)的常見(jiàn)類(多線程編程常用類)的文章就介紹到這了,更多相關(guān)Java JUC常見(jiàn)類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)八個(gè)常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序等
這篇文章主要介紹了Java如何實(shí)現(xiàn)八個(gè)常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序 、快速排序、歸并排序、堆排序和LST基數(shù)排序,需要的朋友可以參考下2015-07-07一文講透為什么遍歷LinkedList要用增強(qiáng)型for循環(huán)
這篇文章主要為大家介紹了為什么遍歷LinkedList要用增強(qiáng)型for循環(huán)的透徹詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04pageHelper一對(duì)多分頁(yè)解決方案示例
這篇文章主要為大家介紹了pageHelper一對(duì)多分頁(yè)解決方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Java中Process類的使用與注意事項(xiàng)說(shuō)明
這篇文章主要介紹了Java中Process類的使用與注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12spring如何集成cxf實(shí)現(xiàn)webservice接口功能詳解
這篇文章主要給大家介紹了關(guān)于spring如何集成cxf實(shí)現(xiàn)webservice接口功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家 的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-07-07淺談maven 多環(huán)境打包發(fā)布的兩種方式
這篇文章主要介紹了淺談maven 多環(huán)境打包發(fā)布的兩種方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法
和MyBatis類似,Spring或者Spring MVC框架在Web應(yīng)用程序的運(yùn)作中同樣主要負(fù)責(zé)處理數(shù)據(jù)庫(kù)事務(wù),這里我們就來(lái)看一下Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法2016-06-06Java中null的意義及其使用時(shí)的注意事項(xiàng)說(shuō)明
這篇文章主要介紹了Java中null的意義及其使用時(shí)的注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09