如今的面試對程序員要求愈來愈高,基礎越紮實拿offer的機率就越高,大廠對基礎仍是很執着的,若是你基礎功底好,面試官是會看中你的潛力,而後去培養的你的。java
除了算法、網絡、計算機基礎等,多線程愈來愈被注重。廢話很少說,直接把題分享給你們。程序員
進程是系統中正在運行的一個程序,程序一旦運行就是進程。面試
進程能夠當作程序執行的一個實例。進程是系統資源分配的獨立實體,每一個進程都擁有獨立的地址空間。一個進程沒法訪問另外一個進程的變量和數據結構,若是想讓一個進程訪問另外一個進程的資源,須要使用進程間通訊,好比管道,文件,套接字等。算法
是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。sql
1.繼承Thread類編程
2.實現Runnable接口數組
3.使用Callable和Future安全
1.start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;經過調用Thread類的start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行操做的, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。 2.run()方法看成普通方法的方式調用。程序仍是要順序執行,要等待run方法體執行完畢後,纔可繼續執行下面的代碼; 程序中只有主線程——這一個線程, 其程序執行路徑仍是隻有一條, 這樣就沒有達到寫線程的目的。bash
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-1
i=2 threadName=Thread-5
i=5 threadName=Thread-2
i=4 threadName=Thread-3
i=3 threadName=Thread-4
複製代碼
雖然println()方法在內部是同步的,但i——————的操做倒是在進入println()以前發生的,因此有發生非線程安全的機率。
println()源碼:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
複製代碼
System.out.println(Thread.currentThread().getName());
複製代碼
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);
複製代碼
能夠看到表達的含義更清晰,更優雅。
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 = 1193854568
high = 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操做直到成功爲止。
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接口來實現鎖功能。
支持重進入的鎖,它表示該鎖可以支持一個線程對資源的重複加鎖。除此以外,該鎖的還支持獲取鎖時的公平和非公平性選擇。
重進入是指任意線程在獲取到鎖以後可以再次獲鎖而不被鎖阻塞。
該特性主要解決如下兩個問題:
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提供的一個用於並行執行任務的框架,把一個大任務分割成若干個小任務,最終彙總每一個小任務結果的後獲得大任務結果的框架。
是指某個線程從其餘隊列裏竊取任務來執行。當大任務被分割成小任務時,有的線程可能提早完成任務,此時閒着不如去幫其餘沒完成工做線程。此時能夠去其餘隊列竊取任務,爲了減小競爭,一般使用雙端隊列,被竊取的線程從頭部拿,竊取的線程從尾部拿任務執行。
優勢:充分利用線程進行並行計算,減小了線程間的競爭。
缺點:有些狀況下仍是存在競爭,好比雙端隊列中只有一個任務。這樣就消耗了更多資源。
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能夠用於遺傳算法。
幾乎全部須要異步或者併發執行任務的程序均可以使用線程池。合理使用會給咱們帶來如下好處。
一、判斷核心線程池裏的線程是否都有在執行任務,否->建立一個新工做線程來執行任務。是->走下個流程。
二、判斷工做隊列是否已滿,否->新任務存儲在這個工做隊列裏,是->走下個流程。
三、判斷線程池裏的線程是否都在工做狀態,否->建立一個新的工做線程來執行任務,
是->走下個流程。
四、按照設置的策略來處理沒法執行的任務。
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:設置建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字。
handler: 飽和策略也叫拒絕策略。當隊列和線程池都滿了,即達到飽和狀態。因此須要採起策略來處理新的任務。默認策略是AbortPolicy。
AbortPolicy:直接拋出異常。
CallerRunsPolicy: 調用者所在的線程來運行任務。
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
DiscardPolicy:不處理,直接丟掉。
固然能夠根據本身的應用場景,實現RejectedExecutionHandler接口自定義策略。
可使用execute()和submit() 兩種方式提交任務。
execute():無返回值,因此沒法判斷任務是否被執行成功。
submit():用於提交須要有返回值的任務。線程池返回一個future類型的對象,經過這個future對象能夠判斷任務是否執行成功,而且能夠經過future的get()來獲取返回值,get()方法會阻塞當前線程知道任務完成。get(long timeout,TimeUnit unit)能夠設置超市時間。
能夠經過shutdown()或shutdownNow()來關閉線程池。它們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt來中斷線程,因此沒法響應終端的任務能夠能永遠沒法中止。
shutdownNow首先將線程池狀態設置成STOP,而後嘗試中止全部的正在執行或者暫停的線程,並返回等待執行任務的列表。
shutdown只是將線程池的狀態設置成shutdown狀態,而後中斷全部沒有正在執行任務的線程。
只要調用二者之一,isShutdown就會返回true,當全部任務都已關閉,isTerminaed就會返回true。
通常來講調用shutdown方法來關閉線程池,若是任務不必定要執行完,能夠直接調用shutdownNow方法。
配置線程池能夠從如下幾個方面考慮。
任務是cpu密集型、IO密集型或者混合型
任務優先級,高中低。
任務時間執行長短。
任務依賴性:是否依賴其餘系統資源。
cpu密集型能夠配置可能小的線程,好比 n + 1個線程。
io密集型能夠配置較多的線程,如 2n個線程。
混合型能夠拆成io密集型任務和cpu密集型任務,
若是兩個任務執行時間相差大,否->分解後執行吞吐量將高於串行執行吞吐量。
否->不必分解。
能夠經過Runtime.getRuntime().availableProcessors()來獲取cpu個數。
建議使用有界隊列,增長系統的預警能力和穩定性。
從JDK5開始,把工做單元和執行機制分開。工做單元包括Runnable和Callable,而執行機制由Executor框架提供。
ThreadPoolExecutor :能夠經過工廠類Executors來建立。
能夠建立3種類型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。
ScheduledThreadPoolExecutor :能夠經過工廠類Executors來建立。
能夠建立2中類型的ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor
Future接口:Future和實現Future接口的FutureTask類來表示異步計算的結果。
Runnable和Callable:它們的接口實現類均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。Runnable不能返回結果,Callable能夠返回結果。
可重用固定線程數的線程池。
查看源碼:
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。
是使用單個worker線程的Executor。
查看源碼:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
corePoolSize和maxnumPoolSize被設置爲1。其餘參數和FixedThreadPool相同。
執行流程以及形成的影響同FixedThreadPool.
根據須要建立新線程的線程池。
查看源碼:
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。
3.在步驟2中新建立的線程將任務執行完後,會執行SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。這個poll操做會讓空閒線程最多在SynchronousQueue中等待60秒鐘。若是60秒鐘內主線程提交了一個新任務(主線程執行步驟1),那麼這個空閒線程將執行主線程提交的新任務;不然,這個空閒線程將終止。因爲空閒60秒的空閒線程會被終止,所以長時間保持空閒的CachedThreadPool不會使用任何資源。
通常來講它適合處理時間短、大量的任務。
參考:
《Java多線程編程核心技術》
《Java高併發編程詳解》
《Java 併發編程的藝術》
博主整理 + 原創 15萬字面試題,包括17個專題。關注「Java小咖秀」回覆「面試」便可得到Java小咖秀面試筆記.pdf