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