今天給大家更新的是一篇關於多線程面試的文章,是根據時下熱門的面試內容給大家進行總結的,如有雷同,請多見諒。 本篇文章屬於乾貨內容!請各位讀者朋友一定要堅持讀到最後,完整閱讀本文後相信你對多線程會有不一樣感悟,下次面試和麵試官也能杠一杠相關內容了。 1.什麼是進程? 進程是系統中正在運行的一個程式,程 ...
今天給大家更新的是一篇關於多線程面試的文章,是根據時下熱門的面試內容給大家進行總結的,如有雷同,請多見諒。
本篇文章屬於乾貨內容!請各位讀者朋友一定要堅持讀到最後,完整閱讀本文後相信你對多線程會有不一樣感悟,下次面試和麵試官也能杠一杠相關內容了。
1.什麼是進程?
進程是系統中正在運行的一個程式,程式一旦運行就是進程。
進程可以看成程式執行的一個實例。進程是系統資源分配的獨立實體,每個進程都擁有獨立的地址空間。一個進程無法訪問另一個進程的變數和數據結構,如果想讓一個進程訪問另一個進程的資源,需要使用進程間通信,比如管道,文件,套接字等。
2.什麼是線程?
是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。
3.線程的實現方式?
1.繼承Thread類
2.實現Runnable介面
3.使用Callable和Future
4.Thread 類中的start() 和 run() 方法有什麼區別?
1.start()方法來啟動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下麵的代碼;通過調用Thread類的start()方法來啟動一個線程, 這時此線程是處於就緒狀態, 並沒有運行。然後通過此Thread類調用方法run()來完成其運行操作的, 這裡方法run()稱為線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。然後CPU再調度其它線程。
2.run()方法當作普通方法的方式調用。程式還是要順序執行,要等待run方法體執行完畢後,才可繼續執行下麵的代碼;程式中只有主線程------這一個線程, 其程式執行路徑還是只有一條, 這樣就沒有達到寫線程的目的。
5.線程NEW狀態
new創建一個Thread對象時,並沒處於執行狀態,因為沒有調用start方法啟動改線程,那麼此時的狀態就是新建狀態。
6.線程RUNNABLE狀態
線程對象通過start方法進入runnable狀態,啟動的線程不一定會立即得到執行,線程的運行與否要看cpu的調度,我們把這個中間狀態叫可執行狀態(RUNNABLE)。
7.線程的RUNNING狀態
一旦cpu通過輪詢貨其他方式從任務可以執行隊列中選中了線程,此時它才能真正的執行自己的邏輯代碼。
8.線程的BLOCKED狀態
線程正在等待獲取鎖。
-
進入BLOCKED狀態,比如調用了sleep,或者wait方法
-
進行某個阻塞的io操作,比如因網路數據的讀寫進入BLOCKED狀態
-
獲取某個鎖資源,從而加入到該鎖的阻塞隊列中而進入BLOCKED狀態
9.線程的TERMINATED狀態
TERMINATED是一個線程的最終狀態,在該狀態下線程不會再切換到其他任何狀態了,代表整個生命周期都結束了。
下麵幾種情況會進入TERMINATED狀態:
-
線程運行正常結束,結束生命周期
-
線程運行出錯意外結束
-
JVM Crash 導致所有的線程都結束
10.線程狀態轉化圖
11.i--與System.out.println()的異常
示例代碼:
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();
}
}
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());
}
}
14.sleep()方法
方法sleep()的作用是在指定的毫秒數內讓當前的"正在執行的線程"休眠(暫停執行)。sleep和wait的5個區別,推薦大家看下。
15.如何優雅的設置睡眠時間?
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?來,教你新姿勢!推薦看下。
16.停止線程
run方法執行完成,自然終止。
stop()方法,suspend()以及resume()都是過期作廢方法,使用它們結果不可預期。
大多數停止一個線程的操作使用Thread.interrupt()等於說給線程打一個停止的標記, 此方法不回去終止一個正在運行的線程,需要加入一個判斷才能可以完成線程的停止。
17.interrupted 和 isInterrupted
interrupted : 判斷當前線程是否已經中斷,會清除狀態。
isInterrupted :判斷線程是否已經中斷,不會清除狀態。
18.yield
放棄當前cpu資源,將它讓給其他的任務占用cpu執行時間。但放棄的時間不確定,有可能剛剛放棄,馬上又獲得cpu時間片。多線程 Thread.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++) {
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 毫秒!
19.線程的優先順序
在操作系統中,線程可以劃分優先順序,優先順序較高的線程得到cpu資源比較多,也就是cpu有限執行優先順序較高的線程對象中的任務,但是不能保證一定優先順序高,就先執行。
Java的優先順序分為1~10個等級,數字越大優先順序越高,預設優先順序大小為5。超出範圍則拋出:java.lang.IllegalArgumentException。
20.優先順序繼承特性
線程的優先順序具有繼承性,比如a線程啟動b線程,b線程與a優先順序是一樣的。
21.誰跑的更快?
設置優先順序高低兩個線程,累加數字,看誰跑的快,上代碼。
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
22.線程種類
Java線程有兩種,一種是用戶線程,一種是守護線程。
23.守護線程的特點
守護線程是一個比較特殊的線程,主要被用做程式中後臺調度以及支持性工作。當Java虛擬機中不存在非守護線程時,守護線程才會隨著JVM一同結束工作。
24.Java中典型的守護線程
GC(垃圾回收器)
25.如何設置守護線程
Thread.setDaemon(true)
PS:Daemon屬性需要再啟動線程之前設置,不能再啟動後設置。
25.Java虛擬機退出時Daemon線程中的finally塊一定會執行?
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。
26.設置線程上下文類載入器
獲取線程上下文類載入器
public ClassLoader getContextClassLoader()
設置線程類載入器(可以打破Java類載入器的父類委托機制)
public void setContextClassLoader(ClassLoader cl)
27.join
join是指把指定的線程加入到當前線程,比如join某個線程a,會讓當前線程b進入等待,直到a的生命周期結束,此期間b線程是處於blocked狀態。
28.什麼是synchronized?
synchronized關鍵字可以時間一個簡單的策略來防止線程干擾和記憶體一致性錯誤,如果一個對象是對多個線程可見的,那麼對該對想的所有讀寫都將通過同步的方式來進行。
29.synchronized包括哪兩個jvm重要的指令?
monitor enter 和 monitor exit
30.synchronized關鍵字用法?
可以用於對代碼塊或方法的修飾,Synchronized 有幾種用法?推薦看下。
31.synchronized鎖的是什麼?
普通同步方法 ---------------> 鎖的是當前實力對象。
靜態同步方法---------------> 鎖的是當前類的Class對象。
同步方法快 ---------------> 鎖的是synchonized括弧里配置的對象。
32.Java對象頭
synchronized用的鎖是存在Java對象頭裡的。對象如果是數組類型,虛擬機用3個字寬(Word)存儲對象頭,如果對象是非數組類型,用2字寬存儲對象頭。
Tips:32位虛擬機中一個字寬等於4位元組。
33.Java對象頭長度
34.Java對象頭的存儲結構
32位JVM的Mark Word 預設存儲結構
35.Mark Word的狀態變化
Mark Word 存儲的數據會隨著鎖標誌為的變化而變化。
64位虛擬機下,Mark Word是64bit大小的
36.鎖的升降級規則
Java SE 1.6 為了提高鎖的性能。引入了"偏向鎖"和輕量級鎖"。
Java SE 1.6 中鎖有4種狀態。級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。
鎖只能升級不能降級。
37.偏向鎖
大多數情況,鎖不僅不存在多線程競爭,而且總由同一線程多次獲得。當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中記錄存儲鎖偏向的線程ID,以後該線程在進入和退出同步塊時不需要進行 cas操作來加鎖和解鎖,只需測試一下對象頭 Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖,如果失敗,則需要測試下Mark Word中偏向鎖的標示是否已經設置成1(表示當前時偏向鎖),如果沒有設置,則使用cas競爭鎖,如果設置了,則嘗試使用cas將對象頭的偏向鎖只想當前線程。
38.關閉偏向鎖延遲
java6和7中預設啟用,但是會在程式啟動幾秒後才激活,如果需要關閉延遲,
-XX:BiasedLockingStartupDelay=0。
39.如何關閉偏向鎖
JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,那麼程式預設會進入輕量級鎖狀態。
Tips:如果你可以確定程式的所有鎖通常情況處於競態,則可以選擇關閉。
40.輕量級鎖
線程在執行同步塊,jvm會現在當前線程的棧幀中創建用於儲存鎖記錄的空間。並將對象頭中的Mark Word複製到鎖記錄中。然後線程嘗試使用cas將對象頭中的Mark Word替換為之鄉鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
41.輕量鎖的解鎖
輕量鎖解鎖時,會使原子操作cas將 displaced Mark Word 替換回對象頭,如果成功則表示沒有競爭發生,如果失敗,表示存在競爭,此時鎖就會膨脹為重量級鎖。
42.鎖的優缺點對比
43.什麼是原子操作
不可被中斷的一個或一系列操作
44.Java如何實現原子操作
Java中通過鎖和迴圈cas的方式來實現原子操作,JVM的CAS操作利用了處理器提供的CMPXCHG指令來實現的。自旋CAS實現的基本思路就是迴圈進行CAS操作直到成功為止。
另外,關註微信公眾號:Java技術棧,在後臺回覆:java,可以獲取我整理的 N 篇 Java 及多線程乾貨。
45.CAS實現原子操作的3大問題
ABA問題,迴圈時間長消耗資源大,只能保證一個共用變數的原子操作
46.什麼是ABA問題
問題:
因為cas需要在操作值的時候,檢查值有沒有變化,如果沒有變化則更新,如果一個值原來是A,變成了B,又變成了A,那麼使用cas進行檢測時會發現發的值沒有發生變化,其實是變過的。
解決:
添加版本號,每次更新的時候追加版本號,A-B-A ---> 1A-2B-3A。
從jdk1.5開始,Atomic包提供了一個類AtomicStampedReference來解決ABA的問題。
47.CAS迴圈時間長占用資源大問題
如果jvm能支持處理器提供的pause指令,那麼效率會有一定的提升。
一、它可以延遲流水線執行指令(de-pipeline),使cpu不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,有些處理器延遲時間是0。
二、它可以避免在退出迴圈的時候因記憶體順序衝突而引起的cpu流水線被清空,從而提高cpu執行效率。
48.CAS只能保證一個共用變數原子操作
一、對多個共用變數操作時,可以用鎖。
二、可以把多個共用變數合併成一個共用變數來操作。比如,x=1,k=a,合併xk=1a,然後用cas操作xk。
Tips:java 1.5開始,jdk提供了AtomicReference類來保證飲用對象之間的原子性,就可以把多個變數放在一個對象來進行cas操作。
49.volatile關鍵字
volatile 是輕量級的synchronized,它在多處理器開發中保證了共用變數的"可見性"。詳細看下這篇文章:volatile關鍵字解析。
Java語言規範第3版對volatile定義如下,Java允許線程訪問共用變數,為了保證共用變數能準確和一致的更新,線程應該確保排它鎖單獨獲得這個變數。如果一個欄位被聲明為volatile,Java線程記憶體模型所有線程看到這個變數的值是一致的。
50.等待/通知機制
一個線程修改了一個對象的值,而另一個線程感知到了變化,然後進行相應的操作。
51.wait
方法wait()的作用是使當前執行代碼的線程進行等待,wait()是Object類通用的方法,該方法用來將當前線程置入"預執行隊列"中,併在 wait()所在的代碼處停止執行,直到接到通知或中斷為止。
在調用wait之前線程需要獲得該對象的對象級別的鎖。代碼體現上,即只能是同步方法或同步代碼塊內。調用wait()後當前線程釋放鎖。
52.notify
notify()也是Object類的通用方法,也要在同步方法或同步代碼塊內調用,該方法用來通知哪些可能燈光該對象的對象鎖的其他線程,如果有多個線程等待,則隨機挑選出其中一個呈wait狀態的線程,對其發出 通知 notify,並讓它等待獲取該對象的對象鎖。
53.notify/notifyAll
notify等於說將等待隊列中的一個線程移動到同步隊列中,而notifyAll是將等待隊列中的所有線程全部移動到同步隊列中。
54.等待/通知經典範式
等待
synchronized(obj) {
while(條件不滿足) {
obj.wait();
}
執行對應邏輯
}
通知
synchronized(obj) {
改變條件
obj.notifyAll();
}
55.ThreadLocal
主要解決每一個線程想綁定自己的值,存放線程的私有數據。
56.ThreadLocal使用
獲取當前的線程的值通過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
57.解決get()返回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技術棧公眾號
58.Lock介面
鎖可以防止多個線程同時共用資源。Java5前程式是靠synchronized實現鎖功能。Java5之後,併發包新增Lock介面來實現鎖功能。
59.Lock介面提供 synchronized不具備的主要特性
60.重入鎖 ReentrantLock
支持重進入的鎖,它表示該鎖能夠支持一個線程對資源的重覆加鎖。除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇。
詳細閱讀:到底什麼是重入鎖,拜托,一次搞清楚!
另外,關註微信公眾號:Java技術棧,在後臺回覆:面試,可以獲取我整理的 N 篇 Java 面試題乾貨。
61.重進入是什麼意思?
重進入是指任意線程在獲取到鎖之後能夠再次獲鎖而不被鎖阻塞。
該特性主要解決以下兩個問題:
一、鎖需要去識別獲取鎖的線程是否為當前占據鎖的線程,如果是則再次成功獲取。
二、所得最終釋放。線程重覆n次是獲取了鎖,隨後在第n次釋放該鎖後,其他線程能夠獲取到該鎖。
62.ReentrantLock預設鎖?
預設非公平鎖
推薦看下:Synchronized 與 ReentrantLock 的區別!
代碼為證:
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;
}
63.公平鎖和非公平鎖的區別
公平性與否針對獲取鎖來說的,如果一個鎖是公平的,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。
64.讀寫鎖
讀寫鎖允許同一時刻多個讀線程訪問,但是寫線程和其他寫線程均被阻塞。讀寫鎖維護一個讀鎖一個寫鎖,讀寫分離,併發性得到了提升。
Java中提供讀寫鎖的實現類是ReentrantReadWriteLock。
65.LockSupport工具
定義了一組公共靜態方法,提供了最基本的線程阻塞和喚醒功能。
66.Condition介面
提供了類似Object監視器方法,與 Lock配合使用實現等待/通知模式。
67.Condition使用
代碼示例:
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();
}
}
}
68.ArrayBlockingQueue?
一個由數據支持的有界阻塞隊列,此隊列FIFO原則對元素進行排序。隊列頭部在隊列中存在的時間最長,隊列尾部存在時間最短。
69.PriorityBlockingQueue?
一個支持優先順序排序的無界阻塞隊列,但它不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。
70.DelayQueue?
是一個支持延時獲取元素的使用優先順序隊列的實現的無界阻塞隊列。隊列中的元素必須實現Delayed介面和 Comparable介面,在創建元素時可以指定多久才能從隊列中獲取當前元素。
71.Java併發容器,你知道幾個?
ConcurrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet 、ConcurrentLinkedQueue、
ConcurrentLinkedDeque、ConcurrentSkipListMap、ConcurrentSkipListSet、ArrayBlockingQueue、
LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、SynchronousQueue、
LinkedTransferQueue、DelayQueue
72.ConcurrentHashMap
併發安全版HashMap,java7中採用分段鎖技術來提高併發效率,預設分16段。Java8放棄了分段鎖,採用CAS,同時當哈希衝突時,當鏈表的長度到8時,會轉化成紅黑樹。(如需瞭解細節,見jdk中代碼)
推薦閱讀:HashMap, ConcurrentHashMap 一次性講清楚!
73.ConcurrentLinkedQueue
基於鏈接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,當我們添加一個元素的時候,它會添加到隊列的尾部,當我們獲取一個元素時,它會返回隊列頭部的元素。它採用cas演算法來實現。(如需瞭解細節,見jdk中代碼)
74.什麼是阻塞隊列?
阻塞隊列是一個支持兩個附加操作的隊列,這兩個附加操作支持阻塞的插入和移除方法。
1、支持阻塞的插入方法:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。
2、支持阻塞的移除方法:當隊列空時,獲取元素的線程會等待隊列變為非空。
75.阻塞隊列常用的應用場景?
常用於生產者和消費者場景,生產者是往隊列里添加元素的線程,消費者是從隊列里取元素的線程。阻塞隊列正好是生產者存放、消費者來獲取的容器。
76.Java里的阻塞的隊列
ArrayBlockingQueue:數組結構組成的 |有界阻塞隊列
LinkedBlockingQueue:鏈表結構組成的|有界阻塞隊列
PriorityBlockingQueue: 支持優先順序排序|無界阻塞隊列
DelayQueue:優先順序隊列實現|無界阻塞隊列
SynchronousQueue:不存儲元素| 阻塞隊列
LinkedTransferQueue:鏈表結構組成|無界阻塞隊列
LinkedBlockingDeque:鏈表結構組成|雙向阻塞隊列
77.Fork/Join
java7提供的一個用於並行執行任務的框架,把一個大任務分割成若幹個小任務,最終彙總每個小任務結果的後得到大任務結果的框架。
詳細閱讀:Java7任務並行執行神器:Fork&Join框架
78.工作竊取演算法
是指某個線程從其他隊列里竊取任務來執行。當大任務被分割成小任務時,有的線程可能提前完成任務,此時閑著不如去幫其他沒完成工作線程。此時可以去其他隊列竊取任務,為了減少競爭,通常使用雙端隊列,被竊取的線程從頭部拿,竊取的線程從尾部拿任務執行。
79.工作竊取演算法的有缺點
優點:充分利用線程進行並行計算,減少了線程間的競爭。
缺點:有些情況下還是存在競爭,比如雙端隊列中只有一個任務。這樣就消耗了更多資源。
80.Java中原子操作更新基本類型,Atomic包提供了哪幾個類?
AtomicBoolean:原子更新布爾類型
AtomicInteger:原子更新整形
AtomicLong:原子更新長整形
81.Java中原子操作更新數組,Atomic包提供了哪幾個類?
AtomicIntegerArray: 原子更新整形數據里的元素
AtomicLongArray: 原子更新長整形數組裡的元素
AtomicReferenceArray: 原子更新飲用類型數組裡的元素
AtomicIntegerArray: 主要提供原子方式更新數組裡的整形
82.Java中原子操作更新引用類型,Atomic包提供了哪幾個類?
如果原子需要更新多個變數,就需要用引用類型了。
AtomicReference : 原子更新引用類型
AtomicReferenceFieldUpdater: 原子更新引用類型里的欄位。
AtomicMarkableReference: 原子更新帶有標記位的引用類型。標記位用boolean類型表示,構造方法時AtomicMarkableReference(V initialRef,boolean initialMark)
83.Java中原子操作更新欄位類,Atomic包提供了哪幾個類?
AtomiceIntegerFieldUpdater: 原子更新整形欄位的更新器
AtomiceLongFieldUpdater: 原子更新長整形欄位的更新器
AtomiceStampedFieldUpdater: 原子更新帶有版本號的引用類型,將整數值
84.JDK併發包中提供了哪幾個比較常見的處理併發的工具類?
提供併發控制手段: CountDownLatch、CyclicBarrier、Semaphore
線程間數據交換: Exchanger
85.CountDownLatch
允許一個或多個線程等待其他線程完成操作。
CountDownLatch的構造函數接受一個int類型的參數作為計數器,你想等待n個點完成,就傳入n。
兩個重要的方法:
countDown() : 調用時,n會減1。
await() : 調用會阻塞當前線程,直到n變成0。
await(long time,TimeUnit unit) : 等待特定時間後,就不會繼續阻塞當前線程。
tips:計數器必須大於等於0,當為0時,await就不會阻塞當前線程。
不提供重新初始化或修改內部計數器的值的功能。
86.CyclicBarrier
可迴圈使用的屏障。
讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續運行。
CyclicBarrier預設構造放時CyclicBarrier(int parities) ,其參數表示屏障攔截的線程數量,每個線程調用await方法告訴CyclicBarrier我已經到達屏障,然後當前線程被阻塞。
87.CountDownLatch與CyclicBarrier區別
CountDownLatch:
計數器:計數器只能使用一次。
等待:一個線程或多個等待另外n個線程完成之後才能執行。
CyclicBarrier:
計數器:計數器可以重置(通過reset()方法)。
等待:n個線程相互等待,任何一個線程完成之前,所有的線程都必須等待。
88.Semaphore
用來控制同時訪問資源的線程數量,通過協調各個線程,來保證合理的公共資源的訪問。
應用場景:流量控制,特別是公共資源有限的應用場景,比如數據鏈接,限流等。
89.Exchanger
Exchanger是一個用於線程間協作的工具類,它提供一個同步點,在這個同步點上,兩個線程可以交換彼此的數據。比如第一個線程執行exchange()方法,它會一直等待第二個線程也執行exchange,當兩個線程都到同步點,就可以交換數據了。
一般來說為了避免一直等待的情況,可以使用exchange(V x,long timeout,TimeUnit unit),設置最大等待時間。
Exchanger可以用於遺傳演算法。
90.為什麼使用線程池
幾乎所有需要非同步或者併發執行任務的程式都可以使用線程池。合理使用會給我們帶來以下好處。
-
降低系統消耗:重覆利用已經創建的線程降低線程創建和銷毀造成的資源消耗。
-
提高響應速度:當任務到達時,任務不需要等到線程創建就可以立即執行。
-
提供線程可以管理性:可以通過設置合理分配、調優、監控。
91.線程池工作流程
1、判斷核心線程池裡的線程是否都有在執行任務,否->創建一個新工作線程來執行任務。是->走下個流程。
2、判斷工作隊列是否已滿,否->新任務存儲在這個工作隊列里,是->走下個流程。
3、判斷線程池裡的線程是否都在工作狀態,否->創建一個新的工作線程來執行任務,
是->走下個流程。
4、按照設置的策略來處理無法執行的任務。
詳細閱讀:java高級應用:線程池全面解析。
92.創建線程池參數有哪些,作用?
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密集型、IO密集型或者混合型
-
任務優先順序,高中低。
-
任務時間執行長短。
-
任務依賴性:是否依賴其他系統資源。
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不會使用任何資源。
一般來說它適合處理時間短、大量的任務。
來源:www.jianshu.com/p/3e88a5fe75f0
推薦去我的博客閱讀更多:
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
覺得不錯,別忘了點贊+轉發哦!