某一天你在面試時(shí)遇到了線程的相關(guān)問(wèn)題。
面試官:“你知道有哪幾種創(chuàng)建線程的方式嗎桶唐?”
(此時(shí)你的心理活動(dòng):哈哈小意思這能難住我栅葡,忍住激動(dòng)假裝淡定道)
你:“嗯,可以通過(guò)實(shí)現(xiàn) Runnable 接口和繼承 Thread 類來(lái)創(chuàng)建線程尤泽⌒来兀”
面試官:“除了這兩種還有其他方式嗎?”
你:“emmm...還有嗎坯约?”
面試官:“知道通過(guò)實(shí)現(xiàn) Callable 接口與獲取 Future 對(duì)象來(lái)實(shí)現(xiàn)嗎熊咽?”
你:“emmm不知道...不過(guò)現(xiàn)在知道了嘻嘻”
面試官:“那創(chuàng)建線程池有哪些方式呢?”
你:“可以通過(guò) ThreadPoolExecutor 構(gòu)造函數(shù)或者 Executors 提供的工廠方法來(lái)創(chuàng)建”
面試官:“那通過(guò)不同的 Executors 工廠方法創(chuàng)建線程池之間有什么區(qū)別呢闹丐?”
你:“emmm...“
面試官:“那 ThreadPoolExecutor 構(gòu)造函數(shù)中的工作隊(duì)列和拒絕策略分別有哪些呢横殴?”
你:“emmm...“
(此時(shí)你的心理活動(dòng):QAQ...不面了,把勞資簡(jiǎn)歷還我卿拴!)
1衫仑、線程的定義
概念:線程是進(jìn)程中執(zhí)行運(yùn)算的最小單位,是進(jìn)程中的一個(gè)實(shí)體堕花,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位文狱,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源缘挽,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源瞄崇。一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行壕曼。
2杠袱、線程6種狀態(tài)及切換
Java中線程的狀態(tài)分為6種,定義在Thread類的State枚舉中窝稿。
public class Thread implements Runnable {
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
...
}
NEW:初始狀態(tài)楣富,創(chuàng)建一個(gè)線程對(duì)象時(shí)就是該狀態(tài)。
RUNNABLE:運(yùn)行狀態(tài)伴榔,它包含了就緒(READY)和運(yùn)行中(RUNNING)兩種狀態(tài)纹蝴。當(dāng)線程對(duì)象創(chuàng)建后,調(diào)用該對(duì)象的 start() 方法就會(huì)進(jìn)入就緒狀態(tài)(READY)踪少。該狀態(tài)的線程位于可運(yùn)行線程池中塘安,等待被線程調(diào)度選中,獲取 CPU 的使用權(quán)援奢,在獲得 CPU 時(shí)間片后會(huì)變?yōu)檫\(yùn)行中狀態(tài)(RUNNING)兼犯。
BLOCKED:阻塞狀態(tài),表示線程此時(shí)阻塞于鎖。
WAITING:等待狀態(tài)切黔,進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)砸脊。
TIMED_WAITING:超時(shí)等待狀態(tài),該狀態(tài)與 WAITING 的不同點(diǎn)在于它可以在指定的時(shí)間后自行返回纬霞。
TERMINATED:終止?fàn)顟B(tài)凌埂,表示該線程已經(jīng)執(zhí)行完。
注意下圖狀態(tài)之間的切換诗芜。
</br>
3瞳抓、線程的四種創(chuàng)建方式
3.1、實(shí)現(xiàn) Runnable 接口
Runnable接口源碼如下伏恐,它只有一個(gè)run()方法孩哑。
public interface Runnable {
public abstract void run();
}
示例:
public class ThreadDemo implements Runnable {
@Override
public void run() {
System.out.println("通過(guò)實(shí)現(xiàn) Runnable 接口創(chuàng)建線程");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
}
}
3.2、繼承 Thread 類
示例:
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("通過(guò)繼承 Thread 類創(chuàng)建線程");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
}
}
3.3翠桦、通過(guò) Callable臭笆、Future
通過(guò)實(shí)現(xiàn) Runnable 接口與繼承 Thread 類的方式創(chuàng)建的線程是沒(méi)有返回值的,然而在有些情況下秤掌,往往需要通過(guò)某個(gè)線程計(jì)算得到的結(jié)果供給另一個(gè)線程使用愁铺,這個(gè)時(shí)候采用Runnable 與 Thread 創(chuàng)建線程并通過(guò)自行編寫(xiě)代碼實(shí)現(xiàn)結(jié)果返回的方式會(huì)不可避免的出現(xiàn)許多錯(cuò)誤或性能上的問(wèn)題∥偶基于此問(wèn)題茵乱,JUC并發(fā)包(java.util.concurrent)提供了解決方案,實(shí)現(xiàn) Callable 的 call() 方法(這個(gè)類似Runnable 接口)孟岛,使用 Future 的 get() 方法進(jìn)行獲取瓶竭。
先來(lái)看一下Callable接口:
public interface Callable<V> {
V call() throws Exception;
}
創(chuàng)建過(guò)程為:
1、自定義一個(gè)類實(shí)現(xiàn)Callable接口渠羞,重寫(xiě)call()方法斤贰;
2、使用JUC包下的 ExecutorService 生成一個(gè)對(duì)象次询,使用 submit() 方法得到 Future 對(duì)象荧恍;
3、采用 Future 的 get() 方法獲取返回值屯吊。
示例:
import java.util.concurrent.*;
/**
* 計(jì)算1+2+...+20的結(jié)果送巡,開(kāi)啟三個(gè)線程,主線程獲取兩個(gè)子線程計(jì)算的結(jié)果盒卸,一個(gè)子線程計(jì)算1+...+10骗爆,一個(gè)子線程計(jì)算11+...+20。
*/
public class ThreadDemo implements Callable {
//子線程1蔽介,用來(lái)計(jì)算1+...+10
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 1; i <= 10; i++)
count = count + i;
return count;
}
public static void main(String[] args) throws Exception {
//生成具有兩個(gè)線程的線程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//調(diào)用 executorService.submit() 方法獲取 Future 對(duì)象
Future<Integer> result1 = executorService.submit(new ThreadDemo());
Future<Integer> result2 = executorService.submit(new SubThread());
//使用 Future的get() 方法等待子線程計(jì)算完成返回的結(jié)果
int result = result1.get() + result2.get();
//關(guān)閉線程池
executorService.shutdown();
System.out.println(result);
}
}
class SubThread implements Callable {
//子線程2摘投,用來(lái)計(jì)算11+...+20
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 11; i <= 20; i++)
count = count + i;
return count;
}
}
3.4煮寡、通過(guò) JUC 里面的線程池
Executors 的工廠方法提供了 5 種不同的線程池,具體可以看JDK API犀呼,如下圖幸撕。
其實(shí)在3.3、通過(guò) Callable圆凰、Future 方式創(chuàng)建線程的示例就能看到通過(guò)Executors.newFixedThreadPool(2) 工廠方法構(gòu)建線程池杈帐。(Executors 的五種工廠方法的區(qū)別及優(yōu)缺點(diǎn)這里就不說(shuō)了体箕,下章再細(xì)說(shuō))
4专钉、幾個(gè)常見(jiàn)的線程面試題
4.1、線程與進(jìn)程的區(qū)別
???????進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的單元累铅,線程是CPU調(diào)度運(yùn)行的單位跃须;一個(gè)進(jìn)程中可以包含很多線程,線程共享進(jìn)程的內(nèi)存等資源娃兽;每個(gè)進(jìn)程擁有各自獨(dú)立的一套變量菇民,相互不影響,而線程則共享數(shù)據(jù)投储,會(huì)存在線程安全問(wèn)題第练。
4.2、Thread 的 sleep() 方法和 wait() 方法有什么區(qū)別和共同點(diǎn)?
相同點(diǎn):
- 兩者都可以暫停線程的執(zhí)行玛荞,都會(huì)讓線程進(jìn)入等待狀態(tài)娇掏。
不同點(diǎn):
- sleep() 方法沒(méi)有釋放鎖,而 wait() 方法釋放了鎖勋眯。
- sleep() 方法屬于 Thread 類的靜態(tài)方法婴梧,作用于當(dāng)前線程;而 wait() 方法是 Object 類的實(shí)例方法客蹋,作用于對(duì)象本身塞蹭。
- 執(zhí)行 sleep() 方法后,可以通過(guò)超時(shí)或者調(diào)用 interrupt() 方法喚醒休眠中的線程讶坯;執(zhí)行 wait() 方法后番电,通過(guò)調(diào)用 notify() 或 notifyAll() 方法喚醒等待線程。
4.3辆琅、為什么啟動(dòng)線程時(shí)是調(diào)用 start() 方法而不是直接調(diào)用 run() 方法钧舌?
???????new 一個(gè) Thread 時(shí),線程會(huì)進(jìn)入初始狀態(tài)涎跨;調(diào)用 start() 方法時(shí)洼冻,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開(kāi)始運(yùn)行了隅很。 start() 會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作撞牢,然后自動(dòng)執(zhí)行 run() 方法的內(nèi)容率碾,這是真正的多線程工作。 而直接執(zhí)行 run() 方法屋彪,會(huì)把 run() 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行所宰,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作畜挥。
一句話總結(jié)就是調(diào)用 start() 方法可啟動(dòng)線程并使線程進(jìn)入就緒狀態(tài)仔粥,而 run() 方法只是 Thread 的一個(gè)普通方法調(diào)用,還是在主線程里執(zhí)行蟹但。
5躯泰、總結(jié)
通過(guò)本文希望能對(duì)你 Java 線程相關(guān)的知識(shí)掌握和面試能有所幫助,下章繼續(xù)介紹線程池的相關(guān)知識(shí)华糖。文中有錯(cuò)誤的地方麦向,還請(qǐng)留言給予指正,謝謝客叉。喜歡文章的小伙伴們可以關(guān)注點(diǎn)贊收藏哦~