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)好后,后廚就開(kāi)始做了.同時(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)有車開(kāi)進(jìn)去的時(shí)候,就相當(dāng)于申請(qǐng)(acquire)一個(gè)可用資源,可用車位就-1.(這稱為信號(hào)量的P操作)
當(dāng)有車開(kāi)出來(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開(kāi)啟公平鎖模式
synchronized是通過(guò)Object的wait/notify實(shí)現(xiàn)等待-喚醒.每次喚醒的是一個(gè)隨機(jī)等待的線程.ReentrantLock搭配Condition類實(shí)現(xiàn)等待-喚醒,可以更精確的控制喚醒某個(gè)指定的線程.
3.信號(hào)量聽(tīng)說(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關(guān)鍵字this(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
java中的this隨處可見(jiàn),用法也多。通常情況下理解this關(guān)鍵字還是很容易的,但是在我初學(xué)的時(shí)候,有一個(gè)疑問(wèn)卻一直不能很清晰的理解,現(xiàn)在慢慢的理解了,下面通過(guò)本文給大家記錄下,有需要的朋友參考下2017-03-03
Spring Boot容器加載時(shí)執(zhí)行特定操作(推薦)
這篇文章主要介紹了Spring Boot容器加載時(shí)執(zhí)行特定操作及spring內(nèi)置的事件,需要的朋友可以參考下2018-01-01
Java根據(jù)開(kāi)始時(shí)間和結(jié)束時(shí)間及周幾計(jì)算日期的示例代碼
在Java 7中,java.time包不存在,所以我們需要使用java.util.Calendar和java.util.Date類來(lái)實(shí)現(xiàn)類似的功能,這篇文章主要介紹了Java根據(jù)開(kāi)始時(shí)間和結(jié)束時(shí)間及周幾計(jì)算出日期的示例代碼,需要的朋友可以參考下2024-06-06
詳細(xì)談?wù)凧ava中l(wèi)ong和double的原子性
原子性是指一個(gè)操作或多個(gè)操作要么全部執(zhí)行,且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行,下面這篇文章主要給大家介紹了關(guān)于Java中l(wèi)ong和double原子性的相關(guān)資料,需要的朋友可以參考下2021-08-08

