本篇文章屬於乾貨內容!請各位讀者朋友必定要堅持讀到最後,完整閱讀本文後相信你對多線程會有不同感悟,下次面試和麪試官也能槓一槓相關內容了。java
進程是系統中正在運行的一個程序,程序一旦運行就是進程。進程能夠當作程序執行的一個實例。進程是系統資源分配的獨立實體,每一個進程都擁有獨立的地址空間。一個進程沒法訪問另外一個進程的變量和數據結構,若是想讓一個進程訪問另外一個進程的資源,須要使用進程間通訊,好比管道,文件,套接字等。面試
是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。算法
1.繼承Thread類2.實現Runnable接口3.使用Callable和Future數組
1.start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;經過調用Thread類的start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並無運行。而後經過此Thread類調用方法run()來完成其運行操做的, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。安全
2.run()方法看成普通方法的方式調用。程序仍是要順序執行,要等待run方法體執行完畢後,纔可繼續執行下面的代碼;程序中只有主線程------這一個線程, 其程序執行路徑仍是隻有一條, 這樣就沒有達到寫線程的目的。微信
new建立一個Thread對象時,並沒處於執行狀態,由於沒有調用start方法啓動改線程,那麼此時的狀態就是新建狀態。網絡
線程對象經過start方法進入runnable狀態,啓動的線程不必定會當即獲得執行,線程的運行與否要看cpu的調度,咱們把這個中間狀態叫可執行狀態(RUNNABLE)。數據結構
一旦cpu經過輪詢貨其餘方式從任務能夠執行隊列中選中了線程,此時它才能真正的執行本身的邏輯代碼。多線程
線程正在等待獲取鎖。併發
TERMINATED是一個線程的最終狀態,在該狀態下線程不會再切換到其餘任何狀態了,表明整個生命週期都結束了。下面幾種狀況會進入TERMINATED狀態:
示例代碼:
public class XkThread extends Thread { private int i = 5; @Override public void run() { System.out.println("i=" + (i------------------) + " threadName=" + Thread.currentThread().getName()); } public static void main(String[] args) { XkThread xk = new XkThread(); Thread t1 = new Thread(xk); Thread t2 = new Thread(xk); Thread t3 = new Thread(xk); Thread t4 = new Thread(xk); Thread t5 = new Thread(xk); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}
結果:
i=5 threadName=Thread-1i=2 threadName=Thread-5i=5 threadName=Thread-2i=4 threadName=Thread-3i=3 threadName=Thread-4
雖然println()方法在內部是同步的,但i------------------的操做倒是在進入println()以前發生的,因此有發生非線程安全的機率。
println()源碼:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
12.如何知道代碼段被哪一個線程調用?
System.out.println(Thread.currentThread().getName());
13.線程活動狀態?
public class XKThread extends Thread { @Override public void run() { System.out.println("run run run is " + this.isAlive() ); } public static void main(String[] args) { XKThread xk = new XKThread(); System.out.println("begin --------- " + xk.isAlive()); xk.start(); System.out.println("end --------------- " + xk.isAlive()); }}
方法sleep()的做用是在指定的毫秒數內讓當前的"正在執行的線程"休眠(暫停執行)。
jdk1.5 後,引入了一個枚舉TimeUnit,對sleep方法提供了很好的封裝。好比要表達2小時22分55秒899毫秒。
Thread.sleep(8575899L);TimeUnit.HOURS.sleep(3);TimeUnit.MINUTES.sleep(22);TimeUnit.SECONDS.sleep(55);TimeUnit.MILLISECONDS.sleep(899);
能夠看到表達的含義更清晰,更優雅。線程休眠只會用 Thread.sleep?來,教你新姿式!推薦看下。
run方法執行完成,天然終止。stop()方法,suspend()以及resume()都是過時做廢方法,使用它們結果不可預期。大多數中止一個線程的操做使用Thread.interrupt()等於說給線程打一箇中止的標記, 此方法不回去終止一個正在運行的線程,須要加入一個判斷才能能夠完成線程的中止。
interrupted : 判斷當前線程是否已經中斷,會清除狀態。isInterrupted :判斷線程是否已經中斷,不會清除狀態。
放棄當前cpu資源,將它讓給其餘的任務佔用cpu執行時間。但放棄的時間不肯定,有可能剛剛放棄,立刻又得到cpu時間片。
測試代碼:(cpu獨佔時間片)
public class XKThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用時 = " + (endTime - beginTime) + " 毫秒! "); } public static void main(String[] args) { XKThread xkThread = new XKThread(); xkThread.start(); }}
結果:
用時 = 20 毫秒!
加入yield,再來測試。(cpu讓給其餘資源致使速度變慢)
public class XKThread extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { Thread.yield(); count = count + (i + 1); } long endTime = System.currentTimeMillis(); System.out.println("用時 = " + (endTime - beginTime) + " 毫秒! "); } public static void main(String[] args) { XKThread xkThread = new XKThread(); xkThread.start(); }}
結果:
用時 = 38424 毫秒!
在操做系統中,線程能夠劃分優先級,優先級較高的線程獲得cpu資源比較多,也就是cpu有限執行優先級較高的線程對象中的任務,可是不能保證必定優先級高,就先執行。Java的優先級分爲1~10個等級,數字越大優先級越高,默認優先級大小爲5。超出範圍則拋出:java.lang.IllegalArgumentException。
線程的優先級具備繼承性,好比a線程啓動b線程,b線程與a優先級是同樣的。
設置優先級高低兩個線程,累加數字,看誰跑的快,上代碼。
public class Run extends Thread{ public static void main(String[] args) { try { ThreadLow low = new ThreadLow(); low.setPriority(2); low.start(); ThreadHigh high = new ThreadHigh(); high.setPriority(8); high.start(); Thread.sleep(2000); low.stop(); high.stop(); System.out.println("low = " + low.getCount()); System.out.println("high = " + high.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } }}class ThreadHigh extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } }}class ThreadLow extends Thread { private int count = 0; public int getCount() { return count; } @Override public void run() { while (true) { count++; } }}
結果:
low = 1193854568high = 1204372373
Java線程有兩種,一種是用戶線程,一種是守護線程。
守護線程是一個比較特殊的線程,主要被用作程序中後臺調度以及支持性工做。當Java虛擬機中不存在非守護線程時,守護線程纔會隨着JVM一同結束工做。
GC(垃圾回收器)
Thread.setDaemon(true)PS:Daemon屬性須要再啓動線程以前設置,不能再啓動後設置。
Java虛擬機退出時Daemon線程中的finally塊並不必定會執行。代碼示例:
public class XKDaemon { public static void main(String[] args) { Thread thread = new Thread(new DaemonRunner(),"xkDaemonRunner"); thread.setDaemon(true); thread.start(); } static class DaemonRunner implements Runnable { @Override public void run() { try { SleepUtils.sleep(10); } finally { System.out.println("Java小咖秀 daemonThread finally run ..."); } } }}
結果:沒有任何的輸出,說明沒有執行finally。
獲取線程上下文類加載器
public ClassLoader getContextClassLoader()
設置線程類加載器(能夠打破Java類加載器的父類委託機制)
public void setContextClassLoader(ClassLoader cl)
join是指把指定的線程加入到當前線程,好比join某個線程a,會讓當前線程b進入等待,直到a的生命週期結束,此期間b線程是處於blocked狀態。
synchronized關鍵字能夠時間一個簡單的策略來防止線程干擾和內存一致性錯誤,若是一個對象是對多個線程可見的,那麼對該對想的全部讀寫都將經過同步的方式來進行。
monitor enter 和 monitor exit
能夠用於對代碼塊或方法的修飾,
普通同步方法 ---------------> 鎖的是當前實力對象。靜態同步方法---------------> 鎖的是當前類的Class對象。同步方法快 ---------------> 鎖的是synchonized括號裏配置的對象。
synchronized用的鎖是存在Java對象頭裏的。對象若是是數組類型,虛擬機用3個字寬(Word)存儲對象頭,若是對象是非數組類型,用2字寬存儲對象頭。Tips:32位虛擬機中一個字寬等於4字節。
32位JVM的Mark Word 默認存儲結構
Mark Word 存儲的數據會隨着鎖標誌爲的變化而變化。
64位虛擬機下,Mark Word是64bit大小的
Java SE 1.6 爲了提升鎖的性能。引入了"偏向鎖"和輕量級鎖"。Java SE 1.6 中鎖有4種狀態。級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。鎖只能升級不能降級。
大多數狀況,鎖不只不存在多線程競爭,並且總由同一線程屢次得到。當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中記錄存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要進行 cas操做來加鎖和解鎖,只需測試一下對象頭 Mark Word裏是否存儲着指向當前線程的偏向鎖。若是測試成功,表示線程已經得到了鎖,若是失敗,則須要測試下Mark Word中偏向鎖的標示是否已經設置成1(表示當前時偏向鎖),若是沒有設置,則使用cas競爭鎖,若是設置了,則嘗試使用cas將對象頭的偏向鎖只想當前線程。
java6和7中默認啓用,可是會在程序啓動幾秒後才激活,若是須要關閉延遲,-XX:BiasedLockingStartupDelay=0。
JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,那麼程序默認會進入輕量級鎖狀態。Tips:若是你能夠肯定程序的全部鎖一般狀況處於競態,則能夠選擇關閉。
線程在執行同步塊,jvm會如今當前線程的棧幀中建立用於儲存鎖記錄的空間。並將對象頭中的Mark Word複製到鎖記錄中。而後線程嘗試使用cas將對象頭中的Mark Word替換爲之鄉鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
輕量鎖解鎖時,會使原子操做cas將 displaced Mark Word 替換回對象頭,若是成功則表示沒有競爭發生,若是失敗,表示存在競爭,此時鎖就會膨脹爲重量級鎖。
不可被中斷的一個或一系列操做
Java中經過鎖和循環cas的方式來實現原子操做,JVM的CAS操做利用了處理器提供的CMPXCHG指令來實現的。自旋CAS實現的基本思路就是循環進行CAS操做直到成功爲止。另外,關注微信公衆號:Java技術棧,在後臺回覆:java,能夠獲取我整理的 N 篇 Java 及多線程乾貨。
ABA問題,循環時間長消耗資源大,只能保證一個共享變量的原子操做
問題:由於cas須要在操做值的時候,檢查值有沒有變化,若是沒有變化則更新,若是一個值原來是A,變成了B,又變成了A,那麼使用cas進行檢測時會發現發的值沒有發生變化,實際上是變過的。解決:添加版本號,每次更新的時候追加版本號,A-B-A ---> 1A-2B-3A。從jdk1.5開始,Atomic包提供了一個類AtomicStampedReference來解決ABA的問題。
若是jvm能支持處理器提供的pause指令,那麼效率會有必定的提高。1、它能夠延遲流水線執行指令(de-pipeline),使cpu不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,有些處理器延遲時間是0。2、它能夠避免在退出循環的時候因內存順序衝突而引發的cpu流水線被清空,從而提升cpu執行效率。
1、對多個共享變量操做時,能夠用鎖。2、能夠把多個共享變量合併成一個共享變量來操做。好比,x=1,k=a,合併xk=1a,而後用cas操做xk。Tips:java 1.5開始,jdk提供了AtomicReference類來保證飲用對象之間的原子性,就能夠把多個變量放在一個對象來進行cas操做。
volatile 是輕量級的synchronized,它在多處理器開發中保證了共享變量的"可見性"。Java語言規範第3版對volatile定義以下,Java容許線程訪問共享變量,爲了保證共享變量能準確和一致的更新,線程應該確保排它鎖單獨得到這個變量。若是一個字段被聲明爲volatile,Java線程內存模型全部線程看到這個變量的值是一致的。
一個線程修改了一個對象的值,而另外一個線程感知到了變化,而後進行相應的操做。
方法wait()的做用是使當前執行代碼的線程進行等待,wait()是Object類通用的方法,該方法用來將當前線程置入"預執行隊列"中,並在 wait()所在的代碼處中止執行,直到接到通知或中斷爲止。在調用wait以前線程須要得到該對象的對象級別的鎖。代碼體現上,即只能是同步方法或同步代碼塊內。調用wait()後當前線程釋放鎖。
notify()也是Object類的通用方法,也要在同步方法或同步代碼塊內調用,該方法用來通知哪些可能燈光該對象的對象鎖的其餘線程,若是有多個線程等待,則隨機挑選出其中一個呈wait狀態的線程,對其發出 通知 notify,並讓它等待獲取該對象的對象鎖。
notify等於說將等待隊列中的一個線程移動到同步隊列中,而notifyAll是將等待隊列中的全部線程所有移動到同步隊列中。
等待
synchronized(obj) { while(條件不知足) { obj.wait(); } 執行對應邏輯}
通知
synchronized(obj) { 改變條件 obj.notifyAll();}
主要解決每個線程想綁定本身的值,存放線程的私有數據。
獲取當前的線程的值經過get(),設置set(T) 方式來設置值。
public class XKThreadLocal { public static ThreadLocal threadLocal = new ThreadLocal(); public static void main(String[] args) { if (threadLocal.get() == null) { System.out.println("未設置過值"); threadLocal.set("Java小咖秀"); } System.out.println(threadLocal.get()); }}
輸出:
未設置過值Java小咖秀
Tips:默認值爲null
經過繼承重寫initialValue()方法便可。代碼實現:
public class ThreadLocalExt extends ThreadLocal{ static ThreadLocalExt threadLocalExt = new ThreadLocalExt(); @Override protected Object initialValue() { return "Java小咖秀"; } public static void main(String[] args) { System.out.println(threadLocalExt.get()); }}
輸出結果:
Java小咖秀
鎖能夠防止多個線程同時共享資源。Java5前程序是靠synchronized實現鎖功能。Java5以後,併發包新增Lock接口來實現鎖功能。
支持重進入的鎖,它表示該鎖可以支持一個線程對資源的重複加鎖。除此以外,該鎖的還支持獲取鎖時的公平和非公平性選擇。詳細閱讀:到底什麼是重入鎖,拜託,一次搞清楚!另外,關注微信公衆號:Java技術棧,在後臺回覆:面試,能夠獲取我整理的 N 篇 Java 面試題乾貨。
重進入是指任意線程在獲取到鎖以後可以再次獲鎖而不被鎖阻塞。該特性主要解決如下兩個問題:1、鎖須要去識別獲取鎖的線程是否爲當前佔據鎖的線程,若是是則再次成功獲取。2、所得最終釋放。線程重複n次是獲取了鎖,隨後在第n次釋放該鎖後,其餘線程可以獲取到該鎖。
默認非公平鎖代碼爲證:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
公平性與否針對獲取鎖來講的,若是一個鎖是公平的,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。
Java中提供讀寫鎖的實現類是ReentrantReadWriteLock。
定義了一組公共靜態方法,提供了最基本的線程阻塞和喚醒功能。
提供了相似Object監視器方法,與 Lock配合使用實現等待/通知模式。
代碼示例:
public class XKCondition { Lock lock = new ReentrantLock(); Condition cd = lock.newCondition(); public void await() throws InterruptedException { lock.lock(); try { cd.await();//至關於Object 方法中的wait() } finally { lock.unlock(); } } public void signal() { lock.lock(); try { cd.signal(); //至關於Object 方法中的notify() } finally { lock.unlock(); } }}
一個由數據支持的有界阻塞隊列,此隊列FIFO原則對元素進行排序。隊列頭部在隊列中存在的時間最長,隊列尾部存在時間最短。
一個支持優先級排序的***阻塞隊列,但它不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。
是一個支持延時獲取元素的使用優先級隊列的實現的***阻塞隊列。隊列中的元素必須實現Delayed接口和 Comparable接口,在建立元素時能夠指定多久才能從隊列中獲取當前元素。
ConcurrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet 、ConcurrentLinkedQueue、 ConcurrentLinkedDeque、ConcurrentSkipListMap、ConcurrentSkipListSet、ArrayBlockingQueue、 LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、SynchronousQueue、 LinkedTransferQueue、DelayQueue
併發安全版HashMap,java7中採用分段鎖技術來提升併發效率,默認分16段。Java8放棄了分段鎖,採用CAS,同時當哈希衝突時,當鏈表的長度到8時,會轉化成紅黑樹。(如需瞭解細節,見jdk中代碼)
基於連接節點的***線程安全隊列,它採用先進先出的規則對節點進行排序,當咱們添加一個元素的時候,它會添加到隊列的尾部,當咱們獲取一個元素時,它會返回隊列頭部的元素。它採用cas算法來實現。(如需瞭解細節,見jdk中代碼)
阻塞隊列是一個支持兩個附加操做的隊列,這兩個附加操做支持阻塞的插入和移除方法。一、支持阻塞的插入方法:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。二、支持阻塞的移除方法:當隊列空時,獲取元素的線程會等待隊列變爲非空。
經常使用於生產者和消費者場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏取元素的線程。阻塞隊列正好是生產者存放、消費者來獲取的容器。
ArrayBlockingQueue:數組結構組成的 |有界阻塞隊列LinkedBlockingQueue:鏈表結構組成的|有界阻塞隊列PriorityBlockingQueue: 支持優先級排序|***阻塞隊列DelayQueue:優先級隊列實現|***阻塞隊列SynchronousQueue:不存儲元素| 阻塞隊列LinkedTransferQueue:鏈表結構組成|***阻塞隊列LinkedBlockingDeque:鏈表結構組成|雙向阻塞隊列
java7提供的一個用於並行執行任務的框架,把一個大任務分割成若干個小任務,最終彙總每一個小任務結果的後獲得大任務結果的框架。詳細閱讀:Java7任務並行執行神器:Fork&Join框架
是指某個線程從其餘隊列裏竊取任務來執行。當大任務被分割成小任務時,有的線程可能提早完成任務,此時閒着不如去幫其餘沒完成工做線程。此時能夠去其餘隊列竊取任務,爲了減小競爭,一般使用雙端隊列,被竊取的線程從頭部拿,竊取的線程從尾部拿任務執行。
優勢:充分利用線程進行並行計算,減小了線程間的競爭。缺點:有些狀況下仍是存在競爭,好比雙端隊列中只有一個任務。這樣就消耗了更多資源。
AtomicBoolean:原子更新布爾類型AtomicInteger:原子更新整形AtomicLong:原子更新長整形
AtomicIntegerArray: 原子更新整形數據裏的元素AtomicLongArray: 原子更新長整形數組裏的元素AtomicReferenceArray: 原子更新飲用類型數組裏的元素AtomicIntegerArray: 主要提供原子方式更新數組裏的整形
若是原子須要更新多個變量,就須要用引用類型了。AtomicReference : 原子更新引用類型AtomicReferenceFieldUpdater: 原子更新引用類型裏的字段。AtomicMarkableReference: 原子更新帶有標記位的引用類型。標記位用boolean類型表示,構造方法時AtomicMarkableReference(V initialRef,boolean initialMark)
AtomiceIntegerFieldUpdater: 原子更新整形字段的更新器AtomiceLongFieldUpdater: 原子更新長整形字段的更新器AtomiceStampedFieldUpdater: 原子更新帶有版本號的引用類型,將整數值
提供併發控制手段:CountDownLatch、CyclicBarrier、Semaphore線程間數據交換: Exchanger
容許一個或多個線程等待其餘線程完成操做。CountDownLatch的構造函數接受一個int類型的參數做爲計數器,你想等待n個點完成,就傳入n。兩個重要的方法:countDown() : 調用時,n會減1。await() : 調用會阻塞當前線程,直到n變成0。await(long time,TimeUnit unit) : 等待特定時間後,就不會繼續阻塞當前線程。tips:計數器必須大於等於0,當爲0時,await就不會阻塞當前線程。不提供從新初始化或修改內部計數器的值的功能。
可循環使用的屏障。讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,全部被屏障攔截的線程纔會繼續運行。CyclicBarrier默認構造放時CyclicBarrier(int parities) ,其參數表示屏障攔截的線程數量,每一個線程調用await方法告訴CyclicBarrier我已經到達屏障,而後當前線程被阻塞。
CountDownLatch:計數器:計數器只能使用一次。等待:一個線程或多個等待另外n個線程完成以後才能執行。CyclicBarrier:計數器:計數器能夠重置(經過reset()方法)。等待:n個線程相互等待,任何一個線程完成以前,全部的線程都必須等待。
用來控制同時訪問資源的線程數量,經過協調各個線程,來保證合理的公共資源的訪問。應用場景:流量控制,特別是公共資源有限的應用場景,好比數據連接,限流等。
Exchanger是一個用於線程間協做的工具類,它提供一個同步點,在這個同步點上,兩個線程能夠交換彼此的數據。好比第一個線程執行exchange()方法,它會一直等待第二個線程也執行exchange,當兩個線程都到同步點,就能夠交換數據了。通常來講爲了不一直等待的狀況,可使用exchange(V x,long timeout,TimeUnit unit),設置最大等待時間。Exchanger能夠用於遺傳算法。
幾乎全部須要異步或者併發執行任務的程序均可以使用線程池。合理使用會給咱們帶來如下好處。
一、判斷核心線程池裏的線程是否都有在執行任務,否->建立一個新工做線程來執行任務。是->走下個流程。二、判斷工做隊列是否已滿,否->新任務存儲在這個工做隊列裏,是->走下個流程。三、判斷線程池裏的線程是否都在工做狀態,否->建立一個新的工做線程來執行任務,是->走下個流程。四、按照設置的策略來處理沒法執行的任務。詳細閱讀:java高級應用:線程池全面解析。
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
1.corePoolSize:核心線程池大小,當提交一個任務時,線程池會建立一個線程來執行任務,即便其餘空閒的核心線程可以執行新任務也會建立,等待須要執行的任務數大於線程核心大小就不會繼續建立。
2.maximumPoolSize:線程池最大數,容許建立的最大線程數,若是隊列滿了,而且已經建立的線程數小於最大線程數,則會建立新的線程執行任務。若是是***隊列,這個參數基本沒用。
3.keepAliveTime: 線程保持活動時間,線程池工做線程空閒後,保持存活的時間,因此若是任務不少,而且每一個任務執行時間較短,能夠調大時間,提升線程利用率。
4.unit: 線程保持活動時間單位,天(DAYS)、小時(HOURS)、分鐘(MINUTES、毫秒MILLISECONDS)、微秒(MICROSECONDS)、納秒(NANOSECONDS)
5.workQueue: 任務隊列,保存等待執行的任務的阻塞隊列。
通常來講能夠選擇以下阻塞隊列:
ArrayBlockingQueue:基於數組的有界阻塞隊列。
LinkedBlockingQueue:基於鏈表的阻塞隊列。
SynchronizedQueue:一個不存儲元素的阻塞隊列。
PriorityBlockingQueue:一個具備優先級的阻塞隊列。
6.threadFactory:設置建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字。
7.handler: 飽和策略也叫拒絕策略。當隊列和線程池都滿了,即達到飽和狀態。因此須要採起策略來處理新的任務。默認策略是AbortPolicy。
AbortPolicy:直接拋出異常。
CallerRunsPolicy: 調用者所在的線程來運行任務。
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
DiscardPolicy:不處理,直接丟掉。
固然能夠根據本身的應用場景,實現RejectedExecutionHandler接口自定義策略。
93.向線程池提交任務
可使用execute()和submit() 兩種方式提交任務。
execute():無返回值,因此沒法判斷任務是否被執行成功。
submit():用於提交須要有返回值的任務。線程池返回一個future類型的對象,經過這個future對象能夠判斷任務是否執行成功,而且能夠經過future的get()來獲取返回值,get()方法會阻塞當前線程知道任務完成。get(long timeout,TimeUnit unit)能夠設置超市時間。
94.關閉線程池
能夠經過shutdown()或shutdownNow()來關閉線程池。它們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt來中斷線程,因此沒法響應終端的任務能夠能永遠沒法中止。
shutdownNow首先將線程池狀態設置成STOP,而後嘗試中止全部的正在執行或者暫停的線程,並返回等待執行任務的列表。
shutdown只是將線程池的狀態設置成shutdown狀態,而後中斷全部沒有正在執行任務的線程。
只要調用二者之一,isShutdown就會返回true,當全部任務都已關閉,isTerminaed就會返回true。
通常來講調用shutdown方法來關閉線程池,若是任務不必定要執行完,能夠直接調用shutdownNow方法。
95.線程池如何合理設置
配置線程池能夠從如下幾個方面考慮。
cpu密集型能夠配置可能小的線程,好比 n + 1個線程。
io密集型能夠配置較多的線程,如 2n個線程。
混合型能夠拆成io密集型任務和cpu密集型任務,
若是兩個任務執行時間相差大,否->分解後執行吞吐量將高於串行執行吞吐量。
否->不必分解。
能夠經過Runtime.getRuntime().availableProcessors()來獲取cpu個數。
建議使用有界隊列,增長系統的預警能力和穩定性。
96.Executor
從JDK5開始,把工做單元和執行機制分開。工做單元包括Runnable和Callable,而執行機制由Executor框架提供。
97.Executor框架的主要成員
ThreadPoolExecutor :能夠經過工廠類Executors來建立。
能夠建立3種類型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。
ScheduledThreadPoolExecutor :能夠經過工廠類Executors來建立。
能夠建立2中類型的ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor
Future接口:Future和實現Future接口的FutureTask類來表示異步計算的結果。
Runnable和Callable:它們的接口實現類均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。Runnable不能返回結果,Callable能夠返回結果。
98.FixedThreadPool
可重用固定線程數的線程池。查看源碼:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}
corePoolSize 和maxPoolSize都被設置成咱們設置的nThreads。
當線程池中的線程數大於corePoolSize ,keepAliveTime爲多餘的空閒線程等待新任務的最長時間,超過這個時間後多餘的線程將被終止,若是設爲0,表示多餘的空閒線程會當即終止。
工做流程:
1.當前線程少於corePoolSize,建立新線程執行任務。
2.當前運行線程等於corePoolSize,將任務加入LinkedBlockingQueue。
3.線程執行完1中的任務,會循環反覆從LinkedBlockingQueue獲取任務來執行。
LinkedBlockingQueue做爲線程池工做隊列(默認容量Integer.MAX_VALUE)。所以可能會形成以下贏下。
1.當線程數等於corePoolSize時,新任務將在隊列中等待,由於線程池中的線程不會超過corePoolSize。
2.maxnumPoolSize等於說是一個無效參數。
3.keepAliveTime等於說也是一個無效參數。
4.運行中的FixedThreadPool(未執行shundown或shundownNow))則不會調用拒絕策略。
5.因爲任務能夠不停的加到隊列,當任務愈來愈多時很容易形成OOM。
99.CachedThreadPool
根據須要建立新線程的線程池。查看源碼:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
corePoolSize設置爲0,maxmumPoolSize爲Integer.MAX_VALUE。keepAliveTime爲60秒。
工做流程:
1.首先執行SynchronousQueue.offer (Runnable task)。若是當前maximumPool 中有空閒線程正在執行S ynchronousQueue.poll(keepAliveTIme,TimeUnit.NANOSECONDS),那麼主線程執行offer操做與空閒線程執行的poll操做配對成功,主線程把任務交給空閒線程執行,execute方 法執行完成;不然執行下面的步驟2。
2.當初始maximumPool爲空或者maximumPool中當前沒有空閒線程時,將沒有線程執行 SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。這種狀況下,步驟 1將失 敗。此時CachedThreadPool會建立一個新線程執行任務,execute()方法執行完成。
3.在步驟2中新建立的線程將任務執行完後,會執行SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。這個poll操做會讓空閒線程最多在SynchronousQueue中等待60秒鐘。若是60秒鐘內主線程提交了一個新任務(主線程執行步驟1),那麼這個空閒線程將執行主線程提交的新任務;不然,這個空閒線程將終止。因爲空閒60秒的空閒線程會被終止,所以長時間保持空閒的CachedThreadPool不會使用任何資源。
通常來講它適合處理時間短、大量的任務。
原做者:Java技術棧
原文連接: 99 道 Java 多線程面試題,看完我跪了!
原出處:公衆號