Java面試準備-執行緒
問題連結轉載 Java面試通關要點彙總集【終極版】
一、建立執行緒的方式及實現
方式有三種:繼承Thread類建立執行緒類,通過Runnable介面建立執行緒類和通過Callable和Future建立執行緒
1)繼承Thread類建立執行緒類
- 定義Thread類的子類,並重寫該類的run()方法
- 建立Thread類的子類,即建立執行緒物件
- 呼叫執行緒物件的start()來啟動執行緒
public Test extends Thread { int i = 0; public void run(){ for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args){ Test t = new Test(); t.start(); } }
2) 通過Runnable介面建立物件
- 定義Runnable介面的實現類,並重寫介面的run()方法
- 建立Runnable實現類的例項,並以此例項作為Thread的target來建立Thread物件
- 呼叫Thread物件的start()啟動執行緒
public class Test implements Runnable{ private int i; @Override public void run() { // TODO Auto-generated method stub System.out.println("hello"); } public static void main(String[] args){ new Thread(new Test()).start(); } }
3)通過Callable和Future建立執行緒
- 建立Callable介面的實現類,並實現call()方法,該方法有返回值
- 建立Callable實現類的例項,使用FutureTask類包裝Callable物件
- 使用FutureTask物件作為Thread物件的target建立並啟動新執行緒
- 呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Beetle implements Callable<Integer>{ public static void main(String[] args){ Beetle ctt = new Beetle(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+" 的迴圈變數i的值"+i); if(i == 20) new Thread(ft,"有返回值的執行緒").start(); } try{ System.out.println("子執行緒的返回值: "+ft.get()); }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); } } @Override public Integer call() throws Exception { // TODO Auto-generated method stub int i = 0; for(;i<100;i++) System.out.println(Thread.currentThread().getName()+" "+i); return i; } }
三種方式的對比
- 採用實現Runnable,Callable介面建立多執行緒時,執行緒類只是實現了Runnable或Callable介面,還可以繼承其他類。這種方式下多個執行緒可以共享同一個target物件。但程式設計稍微複雜,要訪問當前執行緒則必須使用Thread.currentThread()
- 使用繼承Thread類的方式建立多執行緒時,訪問當前執行緒無需使用Thread.currentThrea(),直接使用this即可獲得當前執行緒。但不能繼承其他類
- Callable重寫call(),Runnable重寫run(),Callable任務執行後可返回值,而Runnable的任務沒返回值,call()方法可以丟擲異常,run()方法不可以。
二、sleep() 、join()、wait()、yield()有什麼區別
- sleep():在指定時間內讓當前執行的執行緒暫停執行,但不會釋放"鎖標誌"。不推薦使用。sleep()使用當前執行緒進入阻塞狀態,在指定時間內不會執行。
- wait():在其他執行緒呼叫物件的notify或notifyAll方法前,當前執行緒等待。執行緒會釋放掉它所佔有的鎖標誌。當前執行緒必須擁有當前物件鎖,如果沒有會丟擲IllegalMonitorStateException異常
- yield():暫停當前正在執行的執行緒物件。yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。yield()只能使同優先順序或更高優先順序的執行緒有執行的機會
- join():等待該執行緒終止,等待呼叫join()的執行緒結束,再繼續執行。
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<5;i++)
System.out.println("執行緒1第"+i+"次執行!");
}
}
public class Beetle{
public static void main(String[] args){
Thread t1 = new MyThread1();
t1.start();
for(int i=0;i<10;i++){
System.out.println("主執行緒第"+i+"次執行!");
if( i > 2 ){
try{
t1.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
輸出
執行緒1第0次執行!
執行緒1第1次執行!
執行緒1第2次執行!
主執行緒第0次執行!
主執行緒第1次執行!
主執行緒第2次執行!
執行緒1第3次執行!
執行緒1第4次執行!
主執行緒第3次執行!
主執行緒第4次執行!
主執行緒第5次執行!
主執行緒第6次執行!
主執行緒第7次執行!
主執行緒第8次執行!
主執行緒第9次執行!
三、說說 CountDownLatch 原理
CountDownLatch在多執行緒併發程式設計中充當一個計時器的功能,維護一個count變數,並且其操作都是原子操作,該類主要通過countDown()和await()兩個方法實現功能。首先通過建立CountDownLatch物件,並且傳入引數即count初始值。如果一個執行緒呼叫await(),那麼這個執行緒便進入阻塞狀態,並進入阻塞佇列。如果一個執行緒呼叫了countDown()則會是count-1,當count為0時,阻塞佇列中呼叫await()的執行緒便會逐個被喚醒,執行。
如下所示,讀操作呼叫了await(),而寫操作呼叫countDown(),直到count為0後讀操作才開始
public class Beetle{
private final static CountDownLatch cdl = new CountDownLatch(3);
private final static Vector v = new Vector();
public static class WriteThread extends Thread{
private final String writeThreadName;
private final int stopTime;
private final String str;
public WriteThread(String name,int time,String str){
this.writeThreadName = name;
this.stopTime = time;
this.str = str;
}
public void run(){
System.out.println(writeThreadName+"開始寫入工作");
try{
Thread.sleep(stopTime);
}catch(InterruptedException e){
e.printStackTrace();
}
cdl.countDown();
v.add(str);
System.out.println(writeThreadName+"寫入內容為: "+str+"。寫入工作結束");
}
}
public static class ReadThread extends Thread{
public void run(){
System.out.println("讀操作之前必須先進行寫操作");
try{
cdl.await();
}catch(InterruptedException e){
e.printStackTrace();
}
for(int i=0;i<v.size();i++)
System.out.println("讀取第"+(i+1)+"條記錄內容為: "+v.get(i));
System.out.println("讀操作結束");
}
}
public static void main(String[] args){
new ReadThread().start();
new WriteThread("WriteThread1",1000,"多執行緒知識點").start();
new WriteThread("WriteThread2",2000,"多執行緒CountDownLatch的知識點").start();
new WriteThread("WriteThread3",3000,"多執行緒中控制順序可以使用CountDownLatch").start();
}
}
輸出
讀操作之前必須先進行寫操作
WriteThread1開始寫入工作
WriteThread2開始寫入工作
WriteThread3開始寫入工作
WriteThread1寫入內容為: 多執行緒知識點。寫入工作結束
WriteThread2寫入內容為: 多執行緒CountDownLatch的知識點。寫入工作結束
WriteThread3寫入內容為: 多執行緒中控制順序可以使用CountDownLatch。寫入工作結束
讀取第1條記錄內容為: 多執行緒知識點
讀取第2條記錄內容為: 多執行緒CountDownLatch的知識點
讀取第3條記錄內容為: 多執行緒中控制順序可以使用CountDownLatch
讀操作結束
join()方法也可以實現CountDownLatch的按順序執行,但如果使用執行緒池,執行緒池的執行緒不能直接使用,使用只能用CountDownLatch,而不能用join()
四、說說 CyclicBarrier 原理
CyclicBarrier也叫同步屏障,在JDK1.5被引入,可以讓一組執行緒達到一個屏障時被阻塞,直到最後一個執行緒達到屏障時,之前被阻塞的執行緒才能繼續開始執行。CyclicBarrier有兩個建構函式:
- CyclicBarrier(int parties):只需宣告需要攔截的執行緒數
- CyclicBarrier(int parties , Runnable barrierAction):不僅宣告需要攔截的執行緒數,還要定義一個等待所有執行緒到達屏障優先執行的Runnable物件
其實現原理:在CyclicBarrier的內部定義一個Lock物件,每當一個執行緒呼叫await()方法時,將攔截的執行緒數減1,然後判斷剩餘攔截數是否等於初始值parties,如果不是,進入Lock物件的條件佇列等待。如果是則執行barrierAction物件的Runnable方法,將鎖的條件佇列中的所有執行緒都放入鎖等待佇列中,這些執行緒會依次地獲得鎖,釋放鎖。
public class Beetle{
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
private static final CyclicBarrier cb = new CyclicBarrier(4,new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("寢室四兄弟一起出發去球場");
}
});
private static class GoThread extends Thread{
private final String name;
public GoThread(String name){
this.name = name;
}
public void run(){
System.out.println(name+"開始從宿舍出發");
try{
Thread.sleep(1000);
cb.await();
System.out.println(name+"從樓底下出發");
Thread.sleep(1000);
System.out.println(name+"到達操場");
}catch(InterruptedException e){
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
String[] str = {"李明","王強","劉凱","趙傑"};
for(int i=0;i<4;i++)
threadPool.execute(new GoThread(str[i]));
try{
Thread.sleep(4000);
System.out.println("四人一起到達球場,現在開始打球");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
輸出
李明開始從宿舍出發
趙傑開始從宿舍出發
劉凱開始從宿舍出發
王強開始從宿舍出發
寢室四兄弟一起出發去球場
李明從樓底下出發
趙傑從樓底下出發
劉凱從樓底下出發
王強從樓底下出發
李明到達操場
劉凱到達操場
趙傑到達操場
王強到達操場
四人一起到達球場,現在開始打球
以上便是CyclicBarrier使用例項,通過await()對執行緒攔截,攔截數加1,當攔截數達到初始值parties後首先執行barrierAction,然後對攔截的執行緒佇列依次獲取鎖解放鎖。
CountDownLatch和CyclicBarrier比較
- CountDownLatch是執行緒組之間的等待,即一個或多個執行緒等待N個執行緒完成某件事後再進行;而CyclicBarrier則是執行緒組內的等待,即每個執行緒相互等待,N個執行緒被攔截後再一起依次執行
- CountDownLatch是減計數方式,而CyclicBarrier是加計數方式
- CountDownLatch計數為0無法重置,而CyclicBarrier計數達到初始值後可以重置
- CountDownLatch不可以複用,而CyclicBarrier可以複用
五、說說 Semaphore 原理
Semaphore是一種在多執行緒環境下使用的設施,該設施負責協調各個執行緒,以保證他們能夠正確,合理的使用公共資源的設施,也是作業系統中用於控制程序同步互斥的量。Semaphore是一種計數訊號量,用於管理一組資源,內部是基於AQS的共享模式,相當於給執行緒規定一個量從而控制允許活動的執行緒數。
Semaphore的主要方法:
- Semaphore(int permits):建構函式,建立具有給定許可數的計數訊號量並設定為非公平訊號量
- Semaphore(int permits , boolean fair):建構函式,當fair為true時建立具有給定許可數的計數訊號量並設定為公平訊號量
- void acquire():從此訊號量獲取一個許可的執行緒將一直阻塞
- void acquire(int n):從此訊號量獲取給定數目許可,在提供這些許可前一直將執行緒阻塞
- void release():釋放一個許可,將其返回給訊號量
- void release():釋放n個許可
- int avaliablePermits():當前可用的許可數
public class Beetle{
public static void main(String[] args){
final Semaphore window = new Semaphore(5); // 宣告5個許可
for(int i=0;i<8;i++){
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
try{
window.acquire();
System.out.println(Thread.currentThread().getName()+":開 始買票");
sleep(2000);
System.out.println(Thread.currentThread().getName()+": 購票成功");
window.release();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}.start();
}
}
}
輸出
Thread-0:開 始買票
Thread-3:開 始買票
Thread-4:開 始買票
Thread-2:開 始買票
Thread-1:開 始買票
Thread-2: 購票成功
Thread-0: 購票成功
Thread-5:開 始買票
Thread-4: 購票成功
Thread-3: 購票成功
Thread-7:開 始買票
Thread-6:開 始買票
Thread-1: 購票成功
Thread-5: 購票成功
Thread-7: 購票成功
Thread-6: 購票成功
六、說說 Exchanger 原理
Exchanger(交換者)是用於執行緒間協作的工具類,Exchanger用於進行執行緒間的資料交換。它提供了一個同步點,在這個同步點兩個執行緒可以交換彼此的資料。他們通過exchange()交換資料,如果第一個執行緒先執行exchange(),它會一直等待第二個執行緒也執行exchange(),當兩個執行緒都到達同步點時,兩個執行緒就可以交換資料。因此使用Exchanger的重點是成對的執行緒使用exchange(),當一對執行緒達到同步點就可以進行交換資料
Exchanger類提供了兩個方法:
- String exchange(V x):用於交換,啟動交換並等待另一個執行緒呼叫exchange()
- String exchange(V x , long timeout , TimeUnit unit):用於交換,啟動交換並等待另一個執行緒呼叫exchange(),並且設定了最大等待時間,當等待時間超過timeout則停止等待
public class Beetle{
public static void main(String[] args){
ExecutorService executor = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
executor.execute(new Runnable(){
String data1 = "克拉克森,小拉林庵寺";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "格里芬";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "哈里斯";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.execute(new Runnable(){
String data1 = "科比";
@Override
public void run() {
// TODO Auto-generated method stub
nbaTrade(data1,exchanger);
}
});
executor.shutdown();
}
private static void nbaTrade(String data1,Exchanger exchanger){
try{
System.out.println(Thread.currentThread().getName()+"在交易截止之前把 "+data1+" 交易出去");
Thread.sleep((long)(Math.random()*1000));
String data2 = (String)exchanger.exchange(data1);
System.out.println(Thread.currentThread().getName()+"交易得到 "+data2);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
輸出結果
pool-1-thread-1在交易截止之前把 克拉克森,小拉林庵寺 交易出去
pool-1-thread-2在交易截止之前把 格里芬 交易出去
pool-1-thread-3在交易截止之前把 哈里斯 交易出去
pool-1-thread-4在交易截止之前把 科比 交易出去
pool-1-thread-1交易得到 格里芬
pool-1-thread-2交易得到 克拉克森,小拉林庵寺
pool-1-thread-3交易得到 科比
pool-1-thread-4交易得到 哈里斯
七、說說 CountDownLatch 與 CyclicBarrier 區別
- CountDownLatch是執行緒組之間的等待,即一個或多個執行緒等待N個執行緒完成某件事後再進行;而CyclicBarrier則是執行緒組內的等待,即每個執行緒相互等待,N個執行緒被攔截後再一起依次執行
- CountDownLatch是減計數方式,而CyclicBarrier是加計數方式
- CountDownLatch計數為0無法重置,而CyclicBarrier計數達到初始值後可以重置
- CountDownLatch不可以複用,而CyclicBarrier可以複用
八、ThreadLocal 原理分析
ThreadLocal用於儲存某個執行緒共享變數:對於同一個static ThreadLocal,不同的執行緒只能從中get,set,remove自己的變數,而不會影響其他執行緒的變數
- ThreadLocal.get():獲取ThreadLocal中當前執行緒共享變數的值
- ThreadLocal.set():設定ThreadLocal中當前執行緒共享變數的值
- ThreadLocal.remove():移除ThreadLocal中當前執行緒共享變數的值
- ThreadLocal.initialValue():ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove()後呼叫get(),返回此方法值
public class Beetle{
private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
@Override
protected Object initialValue() {
// TODO Auto-generated method stub
System.out.println("呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值");
return null;
}
};
public static void main(String[] args){
new Thread(new MyIntegerTask("IntegerTask1")).start();
new Thread(new MyStringTask("StringTask1")).start();
new Thread(new MyIntegerTask("IntegerTask2")).start();
new Thread(new MyStringTask("StringTask2")).start();
}
public static class MyIntegerTask implements Runnable{
private String name;
public MyIntegerTask(String name){
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
if(null == Beetle.threadLocal.get()){
Beetle.threadLocal.set(0);
System.out.println("執行緒"+name+": 0");
}else{
int num = (Integer)Beetle.threadLocal.get();
Beetle.threadLocal.set(num+1);
System.out.println("執行緒"+name+": "+Beetle.threadLocal.get());
if(i == 3)
Beetle.threadLocal.remove();
}
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public static class MyStringTask implements Runnable{
private String name;
public MyStringTask(String name){
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
if(null == Beetle.threadLocal.get()){
Beetle.threadLocal.set("a");
System.out.println("執行緒"+name+": a");
}else{
String str = (String)Beetle.threadLocal.get();
Beetle.threadLocal.set(str+"a");
System.out.println("執行緒"+name+": "+Beetle.threadLocal.get());
}
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
輸出
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
執行緒StringTask1: a
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
執行緒StringTask2: a
執行緒IntegerTask1: 0
執行緒IntegerTask2: 0
執行緒StringTask2: aa
執行緒IntegerTask1: 1
執行緒IntegerTask2: 1
執行緒StringTask1: aa
執行緒IntegerTask1: 2
執行緒StringTask1: aaa
執行緒IntegerTask2: 2
執行緒StringTask2: aaa
執行緒IntegerTask2: 3
執行緒IntegerTask1: 3
執行緒StringTask1: aaaa
執行緒StringTask2: aaaa
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
執行緒IntegerTask1: 0
呼叫get()方法時,當前共享變數沒有設定,呼叫initialValue獲取預設值
執行緒IntegerTask2: 0
執行緒StringTask1: aaaaa
執行緒StringTask2: aaaaa
當呼叫ThreadLocal.get()時,實際上時從當前執行緒中獲取ThreadLocalMap<ThreadLocal , Object>,然後根據當前ThreadLocal獲取當前執行緒共享變數Object
九、講講執行緒池的實現原理
使用執行緒池的好處
- 降低資源消耗:可以重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗
- 提供響應速度:當任務到達時,任務可以不需要等到執行緒建立就能立即執行
- 提供執行緒的可管理性:頻繁地建立和銷燬執行緒會降低系統的穩定性,使用執行緒池可以進行統一分配,調優和監控
執行緒池的建立
public ThreadPoolExecutor(
int corePoolSize, // 執行緒池核心執行緒數量
int maximumPoolSize, // 執行緒池最大執行緒數量
long keepAliverTime, // 當活躍執行緒數大於核心執行緒數時,空閒的多餘執行緒最大存活時間
TimeUnit unit, // 存活時間的單位
BlockingQueue<Runnable> workQueue, // 存放任務的佇列
RejectedExecutionHandler handler // 超出執行緒範圍和佇列容量的任務的處理程式
)
三種類型的執行緒池
- newFixedThreadPool,當執行緒池的執行緒數量達到corePoolSize後,即使執行緒池沒有可執行任務時也不會釋放執行緒。它的工作佇列為無界佇列LinkedBlockingQueue(佇列容量為Integer.MAX_VALUE),因此它永遠不會拒絕,即飽和策略失效。
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
- newSingleThreadExecutor,初始化的執行緒中只有一個執行緒,如果該執行緒異常結束後會重新建立一個新的執行緒繼續執行任務,唯一的執行緒可以保證所提交任務的順序執行。由於使用了無界佇列,所以SingleThreadPool永遠不會拒絕,即飽和策略失效
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegateExecutorService(
new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())
);
}
- newCachedThreadPool,執行緒池的執行緒數可達Integer.MAX_VALUE,內部使用SynchronousQueue作為阻塞佇列。與newFixedThreadPool不同,newCachedThreadPool在沒有任務執行時,當執行緒的空閒時間超過keepAliveTime,會自動釋放執行緒資源
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,
TimeUnit.SECONDS,new SynchronousQueue<Runnable>()
);
}
執行過程與前兩種稍微不同:
(1)主執行緒呼叫SynchronousQueue的offer()方法放入task,倘若此時執行緒池中有空閒的執行緒嘗試讀取SynchronousQueue的task,即呼叫了SynchronousQueue的poll(),那麼主執行緒將該task交給空閒執行緒執行,否則執行(2)
(2)當執行緒池為空或者沒有空閒的執行緒則建立新的執行緒執行任務
(3)執行完成任務的執行緒倘若在60s內仍空閒則會終止。因此長時間空閒的CachedThreadPool不會持有任何執行緒資源
執行緒池的實現原理
提交一個任務到執行緒池中,執行緒池的處理流暢如下:
- 當執行緒池小於corePoolSize時,新提交任務將建立一個新執行緒執行任務,即使此時執行緒池中存在空閒執行緒。
- 當執行緒池達到corePoolSize時,新提交任務將被放入workQueue中,等待執行緒池中任務排程執行
- 當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會建立新執行緒執行任務
- 當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
- 當執行緒池中超過corePoolSize執行緒,空閒時間達到keepAliveTime時,關閉空閒執行緒
- 當設定allowCoreThreadTimeOut(true)時,執行緒池中corePoolSize執行緒空閒時間達到keepAliveTime也將關閉
RejectedExecutionHandler:飽和策略
當佇列和執行緒池都滿了,說明執行緒池處於飽和狀態,就要採用飽和策略。預設的策略是AbortPolicy,即無法處理新的任務而丟擲異常。Java的四種策略:
- AbortPolicy:直接丟擲異常
- CallerRunsPolicy:只用呼叫所在的執行緒執行任務
- DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務
- DiscardPolicy:不處理,丟棄掉
備註:
一般如果執行緒池任務佇列採用LinkedBlockingQueue佇列的話,那麼不會拒絕任何任務(因為佇列大小沒有限制),這種情況下,ThreadPoolExecutor最多僅會按照最小執行緒數來建立執行緒,也就是說執行緒池大小被忽略了。
如果執行緒池任務佇列採用ArrayBlockingQueue佇列的話,那麼ThreadPoolExecutor將會採取一個非常負責的演算法,比如假定執行緒池的最小執行緒數為4,最大為8所用的ArrayBlockingQueue最大為10。隨著任務到達並被放到佇列中,執行緒池中最多執行4個執行緒(即最小執行緒數)。即使佇列完全填滿,也就是說有10個處於等待狀態的任務,ThreadPoolExecutor也只會利用4個執行緒。如果佇列已滿,而又有新任務進來,此時才會啟動一個新執行緒,這裡不會因為佇列已滿而拒接該任務,相反會啟動一個新執行緒。新執行緒會執行佇列中的第一個任務,為新來的任務騰出空間。
十、執行緒池的幾種方式與使用場景
- newCachedThreadPool:當有新任務到來則插入到SynchronousQueue中,由於SynchronousQueue是同步佇列,因此會在池中尋找可用執行緒來執行,若有可有執行緒則執行,若沒有則建立一個執行緒來執行該任務;若池中執行緒空閒時間超過指定大小則該執行緒會被銷燬。適用:執行很多短期非同步的小程式或者負載較輕的伺服器
- newFixedThreadPool:建立可容納固定數量執行緒的池子,每個執行緒的存活時間是無限的,當池子滿了就不再新增執行緒了;如果池中的所有執行緒在繁忙狀態,對於新任務會進入阻塞佇列中(無界的阻塞佇列)。適用:執行長期的任務,效能好很多
- newSingleThreadExecutor:建立只有一個執行緒的執行緒池,且執行緒的存活時間是有限的;當該執行緒正繁忙時,對於新任務會進入阻塞佇列(無界的阻塞佇列)。適用:一個任務一個任務執行的場景
- NewScheduledThreadPool:建立一個固定大小的執行緒池,執行緒池內執行緒存活時間無限制,執行緒池可以支援定時及週期性任務執行,如果所有執行緒均處於繁忙狀態,對於新任務會進入DelayedWorkQueue佇列中,這是一種按照超時時間排序的佇列結構。適用:週期性執行任務的場景
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize,Integer.MAX_VALUE,0,TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
十一、執行緒的生命週期
Java執行緒具有5種基本狀態:
- 新建狀態(New):當執行緒物件建立後,即進入新建狀態,如:Thread t = new MyThread()
- 就緒狀態(Runnable):當呼叫執行緒物件的start()方法,執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了start(),此執行緒立即執行
- 執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就緒狀態是進入到執行狀態的唯一入口,即要進入執行狀態前必須是就緒狀態
- 阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行。此時進入阻塞狀態,直到其進入就緒狀態,才有機會再次被CPU呼叫進入執行狀態。阻塞原因可以分為三種:
- 等待阻塞:執行狀態中的執行緒執行wait(),使本執行緒進入到等待阻塞狀態
- 同步阻塞:執行緒在獲取synchronized同步鎖失敗,它會進入同步阻塞狀態
- 其他阻塞:通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入阻塞狀態。當sleep()狀態超時,join()等待執行緒終止或超時,或者I/O處理完畢,執行緒會重新開始
- 死亡狀態(Dead):執行緒執行完了或者因異常退出了run(),該執行緒結束生命週期
注:
就緒狀態轉換為執行狀態:當此執行緒得到處理器資源
執行狀態轉換為就緒狀態:當此執行緒主動呼叫yield()或執行過程中失去處理器資源
執行狀態轉換為死亡狀態:當此執行緒執行完畢或發生異常