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