Java線程簡介

一、併發知識庫

在這裏插入圖片描述

二、Java線程實現及創建方式

2.1繼承Thread類

Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法是通過Thread類的start()的實例方法。start()方法是一個native方法,他將啓動一個新線程,並執行run()方法。

public class MyThreadExtendsThread extends Thread {

	public void run() {
		System.out.println("there is extends Thread!");
	}

}  
 
@Test
public void TestExtendsThread(){
	MyThreadExtendsThread myThreadExtendsThread = new MyThreadExtendsThread();
	myThreadExtendsThread.start();
}

2.2實現Runable接口

如果一個類已經繼承類其他類,則無法繼承Thread類。此時可以實現Runnable接口

public class ImplementsRunable implements Runnable {
	@Override
	public void run() {
		System.out.println("there is implements Runnable!");
	}
} 
@Test
public void TestImplementsRunnable(){
	//啓動一個MyThread需要先實例話一個Thread,傳人自己的MyThread實例
	ImplementsRunable implementsRunable = new ImplementsRunable();
	Thread t = new Thread(implementsRunable);
	t.start();
} 
//事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run()
public void run() {  
	if (target != null) {
  		target.run();  
  	}  
  } 

2.3ExecutorService、Callable、Future有返回值線程

有返回值的任務必須實現Callable接口,類似的,無返回值的任務實現Runnable接口。執行Callable任務後,可以獲得一個Future對象,執行Future對象的get方法就可以會的Callable任務返回的Object了。再接口線程池接口ExecutorService就可以實現有返回結果的多線程任務了。

public void call() {
	//創建一個線程池
	ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
	//創建多個有返回值的任務
	List<Future> futures = new ArrayList<Future>();

	for (int i = 0; i < this.taskSize; i++) {
		Callable callable = new MyCallable(i);
		//執行任務並獲取Future對象
		Future future = executorService.submit(callable);
		futures.add(future);
	}
	// 關閉線程池
	executorService.shutdown();
	futures.forEach(future -> {
		try {
			// 從Future對象上獲取任務的返回值,並輸出到控制檯
			System.out.println("future:" + future.get().toString());
		} catch (InterruptedException | ExecutionException e) {
			System.out.println("future error,e.message " + e.getLocalizedMessage());
		}
	});
}

public class MyCallable implements Callable {

	int i;
	@Override
	public Object call() throws Exception {
		return i;
	}

	public MyCallable(int i) {
		this.i = i;
	}

	public MyCallable() {
	}
}

2.4基於線程池的方式

基於線程池的方式線程和數據庫連接這些資源都是非常寶貴的資源。那麼每次需要的時候創建,不需要的時候銷燬,是非常浪費資源的。那麼我們就可以使用緩存的策略,也就是使用線程池。

// 創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
while(true) {
	executorService.execute(newRunnable() { 
	// 提交多個線程任務,並執行
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + " is running ..");
		try{
			Thread.sleep(3000);
			} catch(InterruptedException e) {
				System.out.println(Thread.currentThread().getName() + "sleep error");
			}
		}
	});
}

三、四種線程池

Java裏線程池的頂級接口是Executors,但嚴格意義上講Executors不是線程池,而是一個執行線程但工具,真正的線程池接口是ExecutorService。在這裏插入圖片描述

3.1newCachedThreadPool

創建一個可根據需要創建線程的線程池,但是再以前創建的線程可用時會重用他們。對於很多執行短期異步任務的程序而言,這些線程池通常可以提高他們的性能。調用executor將重用以前構造的線程(如果他們可用)。如果線程沒有可用的,則創建一個新線程並添加到池中。終止並移除緩存中那些已有60s未被使用的線程。 因此長時間保持空閒的線程池不會佔用大量的系統資源。

3.2newFixedThreadPool

創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。再任意點,在大多數n Threads線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,任務會在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。

3.3newScheduledThreadPool

創建一個線程池,它可在給定延時後執行命令或定期執行。

ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); 
     scheduledThreadPool.schedule(newRunnable(){
        @Override 
        public void run() {
        	System.out.println("延遲三秒");
        }
      },3,TimeUnit.SECONDS);
 scheduledThreadPool.scheduleAtFixedRate(newRunnable(){        
    @Override            
    public void run() {
    	System.out.println("延遲1秒後每三秒執行一次");
    }
 },1,3,TimeUnit.SECONDS);

3.4newSingleThreadExecutor

Executors.newSingleThreadExecutor會創建一個線程池(這個線程池只有一個線程),這個線程池會在線程死亡後(或發生異常時),重新啓動一個新的線程來代替原來的線程繼續執行下去。

四、線程的聲明週期(狀態)

在線程被啓動後,它不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,他會經歷創建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是在線程啓動後,它不可能一直霸佔着cpu獨自運行,所以需要CPU在多條線程之間來回切換,於是線程也會在阻塞、運行之間來回切換。

4.1新建狀態(New)

當程序使用new關鍵字創建一個線程後,該線程就處於新建狀態,此時僅由JVM爲其分配內存,並初始化其成員變量的值。

4.2就緒狀態(Runnable)

當線程方法調用start()方法後,該線程就處於就緒狀態。Java虛擬機會爲其創建方法調用棧和程序計數器,等待調度運行。

4.3運行狀態(Running)

如果處於就緒狀態獲取了CPU,開始執行run()方法的線程體,則該線程處於運行狀態。

4.4阻塞狀態(Blocked)

阻塞狀態是指線程因爲某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,纔有機會再次獲得cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種:

  1. 等待阻塞(o.wait->等待對列)
    運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
  2. 同步阻塞(lock->鎖池)
    運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
  3. 其他阻塞(sleep/join)
    運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。

4.5線程死亡

線程會以下面三種方式結束,結束後就是死亡狀態。
4. 正常結束:run()或call()方法執行完成,線程正常結束。
5. 異常結束:線程拋出一個未捕獲的Exception或Error。
6. 調用stop:直接調用該線程的stop()方法來結束該線程—該方法通常容易導致死鎖,不推薦使用。在這裏插入圖片描述

五、sleep和wait的區別

  1. 對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
  2. sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
  3. 在調用sleep()方法的過程中,線程不會釋放對象鎖。
  4. 而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。

六、start和run的區別

  1. start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下面的代碼。
  2. 通過調用Thread類的start()方法來啓動一個線程,這時此線程是處於就緒狀態,並沒有運行。
  3. 方法run()稱爲線程體,它包含了要執行的這個線程的內容,線程就進入了運行狀態,開始運行run函數當中的代碼。Run方法運行結束,此線程終止。然後CPU再調度其它線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章