1. 程式人生 > >Java面試準備-執行緒

Java面試準備-執行緒

問題連結轉載  Java面試通關要點彙總集【終極版】

一、建立執行緒的方式及實現

方式有三種:繼承Thread類建立執行緒類,通過Runnable介面建立執行緒類和通過Callable和Future建立執行緒

1)繼承Thread類建立執行緒類

  1. 定義Thread類的子類,並重寫該類的run()方法
  2. 建立Thread類的子類,即建立執行緒物件
  3. 呼叫執行緒物件的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介面建立物件

  1. 定義Runnable介面的實現類,並重寫介面的run()方法
  2. 建立Runnable實現類的例項,並以此例項作為Thread的target來建立Thread物件
  3. 呼叫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建立執行緒

  1. 建立Callable介面的實現類,並實現call()方法,該方法有返回值
  2. 建立Callable實現類的例項,使用FutureTask類包裝Callable物件
  3. 使用FutureTask物件作為Thread物件的target建立並啟動新執行緒
  4. 呼叫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不會持有任何執行緒資源

執行緒池的實現原理

提交一個任務到執行緒池中,執行緒池的處理流暢如下:

  1. 當執行緒池小於corePoolSize時,新提交任務將建立一個新執行緒執行任務,即使此時執行緒池中存在空閒執行緒。
  2. 當執行緒池達到corePoolSize時,新提交任務將被放入workQueue中,等待執行緒池中任務排程執行
  3. 當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會建立新執行緒執行任務
  4. 當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
  5. 當執行緒池中超過corePoolSize執行緒,空閒時間達到keepAliveTime時,關閉空閒執行緒
  6. 當設定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呼叫進入執行狀態。阻塞原因可以分為三種:
  1. 等待阻塞:執行狀態中的執行緒執行wait(),使本執行緒進入到等待阻塞狀態
  2. 同步阻塞:執行緒在獲取synchronized同步鎖失敗,它會進入同步阻塞狀態
  3. 其他阻塞:通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入阻塞狀態。當sleep()狀態超時,join()等待執行緒終止或超時,或者I/O處理完畢,執行緒會重新開始
  • 死亡狀態(Dead):執行緒執行完了或者因異常退出了run(),該執行緒結束生命週期

注:

就緒狀態轉換為執行狀態:當此執行緒得到處理器資源

執行狀態轉換為就緒狀態:當此執行緒主動呼叫yield()或執行過程中失去處理器資源

執行狀態轉換為死亡狀態:當此執行緒執行完畢或發生異常