前言
本文會介紹Java中多線程與并發(fā)的基礎(chǔ),適合初學(xué)者食用,如果想看關(guān)于多線程與并發(fā)稍微進(jìn)階一些的內(nèi)容可以看我的另一篇博客— 《鎖》
線程與進(jìn)程的區(qū)別
在計算機發(fā)展初期,每臺計算機是串行地執(zhí)行任務(wù)的,如果碰上需要IO的地方,還需要等待長時間的用戶IO,后來經(jīng)過一段時間有了批處理計算機,其可以批量串行地處理用戶指令,但本質(zhì)還是串行,還是不能并發(fā)執(zhí)行。如何解決并發(fā)執(zhí)行的問題呢?于是引入了進(jìn)程的概念,每個進(jìn)程獨占一份內(nèi)存空間,進(jìn)程是內(nèi)存分配的最小單位,相互間運行互不干擾且可以相互切換,現(xiàn)在我們所看到的多個進(jìn)程“同時"在運行,實際上是進(jìn)程高速切換的效果。
那么有了線程之后,我們的計算機系統(tǒng)看似已經(jīng)很完美了,為什么還要進(jìn)入線程呢?如果一個進(jìn)程有多個子任務(wù),往往一個進(jìn)程需要逐個去執(zhí)行這些子任務(wù),但往往這些子任務(wù)是不相互依賴的,可以并發(fā)執(zhí)行,所以需要CPU進(jìn)行更細(xì)粒度的切換。所以就引入了線程的概念,線程隸屬于某一個進(jìn)程,它共享進(jìn)程的內(nèi)存資源,相互間切換更快速。
進(jìn)程與線程的區(qū)別:
1.進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。所有與進(jìn)程相關(guān)的資源,均被記錄在PCB中。
2.線程隸屬于某一個進(jìn)程,共享所屬進(jìn)程的資源。線程只由堆棧寄存器、程序計數(shù)器和TCB構(gòu)成。
3.進(jìn)程可以看作獨立的應(yīng)用,線程不能看作獨立的應(yīng)用。
4.進(jìn)程有獨立的地址空間,相互不影響,而線程只是進(jìn)程的不同執(zhí)行路徑,如果線程掛了,進(jìn)程也就掛了。所以多進(jìn)程的程序比多線程程序健壯,但是切換消耗資源多。
Java中進(jìn)程與線程的關(guān)系:
1.運行一個程序會產(chǎn)生一個進(jìn)程,進(jìn)程至少包含一個線程。
2.每個進(jìn)程對應(yīng)一個JVM實例,多個線程共享JVM中的堆。
3.Java采用單線程編程模型,程序會自動創(chuàng)建主線程
。
4.主線程可以創(chuàng)建子線程,原則上要后于子線程完成執(zhí)行。
線程的start方法和run方法的區(qū)別
-
區(qū)別
Java中創(chuàng)建線程的方式有兩種,不管使用繼承Thread的方式還是實現(xiàn)Runnable接口的方式,都需要重寫run方法。調(diào)用start方法會創(chuàng)建一個新的線程并啟動,run方法只是啟動線程后的回調(diào)函數(shù),如果調(diào)用run方法,那么執(zhí)行run方法的線程不會是新創(chuàng)建的線程,而如果使用start方法,那么執(zhí)行run方法的線程就是我們剛剛啟動的那個線程。
-
程序驗證
public class Main { public static void main(String[] args) { Thread thread = new Thread(new SubThread()); thread.run(); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("執(zhí)行本方法的線程:"+Thread.currentThread().getName()); } }run和start區(qū)別
Thread和Runnable的關(guān)系
-
Thread源碼
Thread -
Runnable源碼
Runnable -
區(qū)別
通過上述源碼圖,不難看出,Thread是一個類,而Runnable是一個接口,Runnable接口中只有一個沒有實現(xiàn)的run方法,可以得知,Runnable并不能獨立開啟一個線程,而是依賴Thread類去創(chuàng)建線程,執(zhí)行自己的run方法,去執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,才能讓這個類具備多線程的特性。
-
使用繼承Thread方式和實現(xiàn)Runable接口方式分別創(chuàng)建子線程
-
使用繼承Thread類方式創(chuàng)建子線程
public class Main extends Thread{ public static void main(String[] args) { Main main = new Main(); main.start(); } @Override public void run() { System.out.println("通過繼承Thread接口方式創(chuàng)建子線程成功,當(dāng)前線程名:"+Thread.currentThread().getName()); } }運行結(jié)果:
Thread方式創(chuàng)建 -
使用實現(xiàn)Runnable接口方式創(chuàng)建子線程
public class Main{ public static void main(String[] args) { SubThread subThread = new SubThread(); Thread thread = new Thread(subThread); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("通過實現(xiàn)Runnable接口創(chuàng)建子線程成功,當(dāng)前線程名:"+Thread.currentThread().getName()); } }運行結(jié)果:
Runnable方式創(chuàng)建 -
使用匿名內(nèi)部類方式創(chuàng)建子線程
public class Main{ public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("使用匿名內(nèi)部類方式創(chuàng)建線程成功,當(dāng)前線程名:"+Thread.currentThread().getName()); } }); thread.start(); } }運行結(jié)果:
匿名內(nèi)部類方式創(chuàng)建
-
-
關(guān)系
1.Thread是實現(xiàn)了Runnable接口的類,使得run支持多線程。2
2.因類的單一繼承原則,推薦使用Runnable接口,可以使程序更加靈活。
如何實現(xiàn)處理多線程的返回值
通過剛才的學(xué)習(xí),我們知道多線程的邏輯需要放到run方法中去執(zhí)行,而run方法是沒有返回值的,那么遇到需要返回值的狀況就不好解決,那么如何實現(xiàn)子線程返回值呢?
-
主線程等待法
通過讓主線程等待,直到子線程運行完畢為止。
實現(xiàn)方式:
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子線程執(zhí)行完畢"; } }); thread.start(); //如果子線程還未對str進(jìn)行賦值,則一直輪轉(zhuǎn) while(str==null) {} System.out.println(str); } } -
使用Thread中的join()方法
join()方法可以阻塞當(dāng)前線程以等待子線程處理完畢。
實現(xiàn)方式:
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子線程執(zhí)行完畢"; } }); thread.start(); //如果子線程還未對str進(jìn)行賦值,則一直輪轉(zhuǎn) try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(str); } }join方法能做到比主線程等待法更精準(zhǔn)的控制,但是join方法的控制粒度并不夠細(xì)。比如,我需要控制子線程將字符串賦一個特定的值時,再執(zhí)行主線程,這種操作join方法是沒有辦法做到的。
-
通過Callable接口實現(xiàn):通過FutureTask或者線程池獲取
在JDK1.5之前,線程是沒有返回值的,通常程序猿需要獲取子線程返回值頗費周折,現(xiàn)在Java有了自己的返回值線程,即實現(xiàn)了Callable接口的線程,執(zhí)行了實現(xiàn)Callable接口的線程之后,可以獲得一個Future對象,在該對象上調(diào)用一個get方法,就可以執(zhí)行子線程的邏輯并獲取返回的Object。
實現(xiàn)方式1(直接獲取 錯誤示例):
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是帶返回值的子線程"; return str; } public static void main(String[] args) { Main main = new Main(); try { String str = main.call(); /*這種方式為什么是錯誤方式? 和上文說的一樣,run()方法和start()方法的區(qū)別就在于 run()方法是線程啟動后的回調(diào)方法,如果直接調(diào)用,相當(dāng)于沒有創(chuàng)建這個線程 還是由主線程去執(zhí)行。 所以這里的call也一樣,如果直接調(diào)用call,并沒有子線程被創(chuàng)建, 而是相當(dāng)于直接調(diào)用了類中的實例方法,獲取了返回值, 從頭到尾并沒有子線程的存在。*/ System.out.println(str); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }運行結(jié)果:
直接獲取實現(xiàn)方式2(使用FutureTask):
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是帶返回值的子線程"; return str; } public static void main(String[] args) { FutureTask<String> task = new FutureTask<String>(new Main()); new Thread(task).start(); try { if(!task.isDone()) { System.out.println("任務(wù)沒有執(zhí)行完成"); } System.out.println("等待中..."); Thread.sleep(3000); System.out.println(task.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }運行結(jié)果:
使用FutureTask實現(xiàn)方法3(使用線程池配合Future獲?。?/strong>:
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是帶返回值的子線程"; return str; } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); Future<String> future = newCacheThreadPool.submit(new Main()); if(!future.isDone()) { System.out.println("線程尚未執(zhí)行結(jié)束"); } System.out.println("等待中"); Thread.sleep(300); System.out.println(future.get()); newCacheThreadPool.shutdown(); } }運行結(jié)果:
使用線程池配合Future
線程的狀態(tài)
Java線程主要分為以下六個狀態(tài):新建態(tài)(new),運行態(tài)(Runnable),無限期等待(Waiting),限期等待(TimeWaiting),阻塞態(tài)(Blocked),結(jié)束(Terminated)。
-
新建(new)
新建態(tài)是線程處于已被創(chuàng)建但沒有被啟動的狀態(tài),在該狀態(tài)下的線程只是被創(chuàng)建出來了,但并沒有開始執(zhí)行其內(nèi)部邏輯。
-
運行(Runnable)
運行態(tài)分為Ready和Running,當(dāng)線程調(diào)用start方法后,并不會立即執(zhí)行,而是去爭奪CPU,當(dāng)線程沒有開始執(zhí)行時,其狀態(tài)就是Ready,而當(dāng)線程獲取CPU時間片后,從Ready態(tài)轉(zhuǎn)為Running態(tài)。
-
等待(Waiting)
處于等待狀態(tài)的線程不會自動蘇醒,而只有等待被其它線程喚醒,在等待狀態(tài)中該線程不會被CPU分配時間,將一直被阻塞。以下操作會造成線程的等待:
1.沒有設(shè)置timeout參數(shù)的Object.wait()方法。
2.沒有設(shè)置timeout參數(shù)的Thread.join()方法。
3.LockSupport.park()方法(實際上park方法并不是LockSupport提供的,而是在Unsafe中,LockSupport只是對其做了一層封裝,可以看我的另一篇博客《鎖》,里面對于ReentrantLock的源碼解析有提到這個方法)。
-
限期等待(TimeWaiting)
處于限期等待的線程,CPU同樣不會分配時間片,但存在于限期等待的線程無需被其它線程顯式喚醒,而是在等待時間結(jié)束后,系統(tǒng)自動喚醒。以下操作會造成線程限時等待:
1.Thread.sleep()方法。
2.設(shè)置了timeout參數(shù)的Object.wait()方法。
3.設(shè)置了timeout參數(shù)的Thread.join()方法。
4.LockSupport.parkNanos()方法。
5.LockSupport.parkUntil()方法。
-
阻塞(Blocked)
當(dāng)多個線程進(jìn)入同一塊共享區(qū)域時,例如Synchronized塊、ReentrantLock控制的區(qū)域等,會去整奪鎖,成功獲取鎖的線程繼續(xù)往下執(zhí)行,而沒有獲取鎖的線程將進(jìn)入阻塞狀態(tài),等待獲取鎖。
-
結(jié)束(Terminated)
已終止線程的線程狀態(tài),線程已結(jié)束執(zhí)行。
Sleep和Wait的區(qū)別
Sleep和Wait者兩個方法都可以使線程進(jìn)入限期等待的狀態(tài),那么這兩個方法有什么區(qū)別呢?
1.sleep方法由Thread提供,而wait方法由Object提供。
2.sleep方法可以在任何地方使用,而wait方法只能在synchronized塊或synchronized方法中使用(因為必須獲wait方法會釋放鎖,只有獲取鎖了才能釋放鎖)。
3.sleep方法只會讓出CPU,不會釋放鎖,而wait方法不僅會讓出CPU,還會釋放鎖。
測試代碼:
public class Main{
public static void main(String[] args) {
Thread threadA = new Thread(new ThreadA());
Thread threadB = new Thread(new ThreadB());
threadA.setName("threadA");
threadB.setName("threadB");
threadA.start();
threadB.start();
}
public static synchronized void print() {
System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行Sleep");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行Wait");
try {
Main.class.wait(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");
}
}
class ThreadA implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Main.print();
}
}
class ThreadB implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Main.print();
}
}
執(zhí)行結(jié)果:
從上面的結(jié)果可以分析出:當(dāng)線程A執(zhí)行sleep后,等待一秒被喚醒后繼續(xù)持有鎖,執(zhí)行之后的代碼,而執(zhí)行wait之后,立即釋放了鎖,不僅讓出了CPU還讓出了鎖,而后線程B立即持有鎖開始執(zhí)行,和線程A執(zhí)行了同樣的步驟,當(dāng)線程B執(zhí)行wait方法之后,釋放鎖,然后線程A拿到鎖打印了第一個執(zhí)行完畢,然后線程B打印執(zhí)行完畢。
notify和notifyAll的區(qū)別
-
notify
notify可以喚醒一個處于等待狀態(tài)的線程,上代碼:
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notify(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢"); } }執(zhí)行結(jié)果:
notify代碼解釋:線程A在開始執(zhí)行時立即調(diào)用wait進(jìn)入無限等待狀態(tài),如果沒有別的線程來喚醒它,它將一直等待下去,所以此時B持有鎖開始執(zhí)行,并且在執(zhí)行完畢時調(diào)用了notify方法,該方法可以喚醒wait狀態(tài)的A線程,于是A線程蘇醒,開始執(zhí)行剩下的代碼。
-
notifyAll
notifyAll可以用于喚醒所有等待的線程,使所有處于等待狀態(tài)的線程都變?yōu)閞eady狀態(tài),去重新爭奪鎖。
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notifyAll(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢"); } }執(zhí)行結(jié)果:
notifyAll要喚醒前一個例子中的線程A,不光notify方法可以做到,調(diào)用notifyAll方法同樣也可以做到,那么兩者有什么區(qū)別呢?
-
區(qū)別
要說清楚他們的區(qū)別,首先要簡單的說一下Java synchronized的一些原理,在openjdk中查看java的源碼可以看到,java對象中存在monitor鎖,monitor對象中包含鎖池和等待池(這部分的詳細(xì)內(nèi)容在另一篇文章《鎖》中有詳細(xì)介紹,這里就簡單說一說)
鎖池,假設(shè)有多個對象進(jìn)入synchronized塊爭奪鎖,而此時已經(jīng)有一個對象獲取到了鎖,那么剩余爭奪鎖的對象將直接進(jìn)入鎖池中。
等待池,假設(shè)某個線程調(diào)用了對象的wait方法,那么這個線程將直接進(jìn)入等待池,而等待池中的對象不會去爭奪鎖,而是等待被喚醒。
下面可以說notify和notifyAll的區(qū)別了:
notifyAll會讓所有處于等待池中的線程全部進(jìn)入鎖池去爭奪鎖,而notify只會隨機讓其中一個線程去爭奪鎖。
yield方法
-
概念
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();yield源碼上有一段長長的注釋,其大意是說:當(dāng)前線程調(diào)用yield方法時,會給當(dāng)前線程調(diào)度器一個暗示,當(dāng)前線程愿意讓出CPU的使用,但是它的作用應(yīng)結(jié)合詳細(xì)的分析和測試來確保已經(jīng)達(dá)到了預(yù)期的效果,因為調(diào)度器可能會無視這個暗示,使用這個方法是不那么合適的,或許在測試環(huán)境中使用它會比較好。
測試:
public class Main{ public static void main(String[] args) { Thread threadA = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadA正在執(zhí)行yield"); Thread.yield(); System.out.println("ThreadA執(zhí)行yield方法完成"); } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadB正在執(zhí)行yield"); Thread.yield(); System.out.println("ThreadB執(zhí)行yield方法完成"); } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); }測試結(jié)果:
yieldAyieldB可以看出,存在不同的測試結(jié)果,這里選出兩張。
第一種結(jié)果:線程A執(zhí)行完yield方法,讓出cpu給線程B執(zhí)行。然后兩個線程繼續(xù)執(zhí)行剩下的代碼。
第二種結(jié)果:線程A執(zhí)行yield方法,讓出cpu給線程B執(zhí)行,但是線程B執(zhí)行yield方法后并沒有讓出cpu,而是繼續(xù)往下執(zhí)行,此時就是系統(tǒng)無視了這個暗示。
interrupt方法
-
中止線程
interrupt函數(shù)可以中斷一個線程,在interrupt之前,通常使用stop方法來終止一個線程,但是stop方法過于暴力,它的特點是,不論被中斷的線程之前處于一個什么樣的狀態(tài),都無條件中斷,這會導(dǎo)致被中斷的線程后續(xù)的一些清理工作無法順利完成,引發(fā)一些不必要的異常和隱患,還有可能引發(fā)數(shù)據(jù)不同步的問題。
-
溫柔的interrupt方法
interrupt方法的原理與stop方法相比就顯得溫柔的多,當(dāng)調(diào)用interrupt方法去終止一個線程時,它并不會暴力地強制終止線程,而是通知這個線程應(yīng)該要被中斷了,和yield一樣,這也是一種暗示,至于是否應(yīng)該中斷,由被中斷的線程自己去決定。當(dāng)對一個線程調(diào)用interrupt方法時:
1.如果該線程處于被阻塞狀態(tài),則立即退出阻塞狀態(tài),拋出InterruptedException異常。
2.如果該線程處于running狀態(tài),則將該線程的中斷標(biāo)志位設(shè)置為true,被設(shè)置的線程繼續(xù)運行,不受影響,當(dāng)運行結(jié)束時由線程決定是否被中斷。
線程池
線程池的引入是用來解決在日常開發(fā)的多線程開發(fā)中,如果開發(fā)者需要使用到非常多的線程,那么這些線程在被頻繁的創(chuàng)建和銷毀時,會對系統(tǒng)造成一定的影響,有可能系統(tǒng)在創(chuàng)建和銷毀這些線程所耗費的時間會比完成實際需求的時間還要長。另外,在線程很多的狀況下,對線程的管理就形成了一個很大的問題,開發(fā)者通常要將注意力從功能上轉(zhuǎn)移到對雜亂無章的線程進(jìn)行管理上,這項動作實際上是非常耗費精力的。
-
利用Executors創(chuàng)建不同的線程池滿足不同場景的需求
-
newFixThreadPool(int nThreads)
指定工作線程數(shù)量的線程池。
-
newCachedThreadPool()
處理大量中斷事件工作任務(wù)的線程池,
1.試圖緩存線程并重用,當(dāng)無緩存線程可用時,就會創(chuàng)建新的工作線程。
2.如果線程閑置的時間超過閾值,則會被終止并移出緩存。
3.系統(tǒng)長時間閑置的時候,不會消耗什么資源。
-
newSingleThreadExecutor()
創(chuàng)建唯一的工作線程來執(zhí)行任務(wù),如果線程異常結(jié)束,會有另一個線程取代它。可保證順序執(zhí)行任務(wù)。
-
newSingleThreadScheduledExecutor()與newScheduledThreadPool(int corePoolSize)
定時或周期性工作調(diào)度,兩者的區(qū)別在于前者是單一工作線程,后者是多線程
-
newWorkStealingPool()
內(nèi)部構(gòu)建ForkJoinPool,利用working-stealing算法,并行地處理任務(wù),不保證處理順序。
Fork/Join框架:把大任務(wù)分割稱若干個小任務(wù)并行執(zhí)行,最終匯總每個小任務(wù)后得到大任務(wù)結(jié)果的框架。
-
-
為什么要使用線程池
線程是稀缺資源,如果無限制地創(chuàng)建線程,會消耗系統(tǒng)資源,而線程池可以代替開發(fā)者管理線程,一個線程在結(jié)束運行后,不會銷毀線程,而是將線程歸還線程池,由線程池再進(jìn)行管理,這樣就可以對線程進(jìn)行復(fù)用。
所以線程池不但可以降低資源的消耗,還可以提高線程的可管理性。
-
使用線程池啟動線程
public class Main{ public static void main(String[] args) { ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10); newFixThreadPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("通過線程池啟動線程成功"); } }); newFixThreadPool.shutdown(); } } -
新任務(wù)execute執(zhí)行后的判斷
要知道這個點首先要先說說ThreadPoolExecutor的構(gòu)造函數(shù),其中有幾個參數(shù):
1.corePoolSize:核心線程數(shù)量。
2.maximumPoolSize:線程不夠用時能創(chuàng)建的最大線程數(shù)。
3.workQueue:等待隊列。
那么新任務(wù)提交后會執(zhí)行下列判斷:
1.如果運行的線程少于corePoolSize,則創(chuàng)建新線程來處理任務(wù),即時線程池中的其它線程是空閑的。
2.如果線程池中的數(shù)量大于等于corePoolSize且小于maximumPoolSize,則只有當(dāng)workQueue滿時,才創(chuàng)建新的線程去處理任務(wù)。
3.如果設(shè)置的corePoolSize和maximumPoolSize相同,則創(chuàng)建的線程池大小是固定的,如果此時有新任務(wù)提交,若workQueue未滿,則放入workQueue,等待被處理。
4.如果運行的線程數(shù)大于等于maximumPoolSize,maximumPoolSize,這時如果workQueue已經(jīng)滿了,則通過handler所指定的策略來處理任務(wù)。
-
handler 線程池飽和策略
AbortPolicy:直接拋出異常,默認(rèn)。
CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務(wù)。
DiscardOldestPolicy:丟棄隊列中靠最前的任務(wù),并執(zhí)行當(dāng)前任務(wù)。
DiscardPolicy:直接丟棄任務(wù)
自定義。
-
線程池的大小如何選定
這個問題并不是什么秘密,在網(wǎng)上各大技術(shù)網(wǎng)站均有文章說明,我就拿一個最受認(rèn)可的寫上吧
CPU密集型:線程數(shù) = 核心數(shù)或者核心數(shù)+1
IO密集型:線程數(shù) = CPU核數(shù)*(1+平均等待時間/平均工作時間)
當(dāng)然這個也不能完全依賴這個公式,更多的是要依賴平時的經(jīng)驗來操作,這個公式也只是僅供參考而已。
結(jié)語
本文提供了一些Java多線程和并發(fā)方面最最基礎(chǔ)的知識,適合初學(xué)者了解Java多線程的一些基本知識,如果想了解更多的關(guān)于并發(fā)方面的內(nèi)容可以看我的另一篇博客 《鎖》。
歡迎大家訪問我的個人博客:Object's Blog