java進階:15.1 多線程 - Thread、Executor

1. 進程、線程概念

  1. 首先要理解進程(Processor)和線程(Thread)的區別:
  • 進程:是執行中一段程序,即一旦程序被載入到內存中並準備執行,它就是一個進程。進程是表示資源分配的的基本概念,又是調度運行的基本單位,是系統中的併發執行的單位。

  • 線程:單個進程中執行中每個任務就是一個線程。線程是進程中執行運算的最小單位。

  1. 一個線程只能屬於一個進程,但是一個進程可以擁有多個線程。多線程處理就是允許一個進程中在同一時刻執行多個任務。

  2. 線程沒有地址空間,線程包含在進程的地址空間中。線程上下文只包含一個堆棧、一個寄存器、一個優先權,線程文本包含在他的進程 的文本片段中,進程擁有的所有資源都屬於線程。所有的線程共享進程的內存和資源。 同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段, 寄存器的內容,棧段又叫運行時段,用來存放所有局部變量和臨時變量。
     

2. 創建任務和線程

可以在程序中 創建附加的線程 以執行併發任務。在Java 中,每個任務都是 Runnable 接口的一個實例,也稱爲可運行對象(runnable object) 。線程本質上講就是便於任務執行的對象。

  1. 定義一個實現Runnable 接口的類
    任務就是對象。爲了創建任務,必須首先爲任務定義一個實現Runnable 接口的類。Runnable 接口非常簡單,它只包含一個run 方法。需要實現這個方法來告訴系統線程將如何運行。

  2. 創建一個任務
    一旦定義了一個TaskClass ,就可以用它的構造方法創建一個任務。

  3. 創建任務的線程:
    任務必須在線程中執行。Thread 類包括創建線程的構造方法以及控制線程的很多有用的方法。使用下面的語句創建任務的線程:
    Thread thread = new Thread(task);

  4. 調用start()方法
    然後調用start()方法告訴Java 虛擬機該線程準備運行。 thread.start();

  5. JVM調用run()方法執行任務


package Thread;
public class test {
	public static void main(String[] args) {
		Runnable printA = new PrintChar('a',100);  // 第 2 步
		Runnable printB = new PrintChar('b',100);
		Runnable print100 = new PrintNum(100);
		
		Thread thread1 = new Thread(printA);  // 第 3 步
		Thread thread2 = new Thread(printB);
		Thread thread3 = new Thread(print100);
		
		thread1.start();   // 第 4 步
		thread2.start();
		thread3.start();
	}
}
	
class PrintChar implements Runnable{  // 第 1 步
	private char charToPrint;
	private int times;
	public PrintChar(char c,int t) {
		charToPrint = c;
		times = t;
	}	
	@Override
	public void run() {
		for(int i = 0; i < times; i++)
			System.out.print(charToPrint);
	}
}
	
class PrintNum implements Runnable{
	private int lastNum;
	public PrintNum(int n) {
		lastNum = n;
	}	
	@Override
	public void run() {
		for(int i = 1; i <= lastNum; i++) {
			System.out.print(" " + i);
		}
	}
}

 

3. Thread類

在這裏插入圖片描述
Thread.sleep(1000); 表示當前線程暫停1000毫秒 ,其他線程不受影響
Thread.sleep(1000); 會拋出InterruptedException 中斷異常,因爲當前線程sleep的時候,有可能被停止,這時就會拋出 InterruptedException

Thread.yield(); 臨時暫停,使得其他線程可以佔用CPU資源

t1.setDaemon(true); 守護線程就相當於那些支持部門,如果一個進程只剩下守護線程,那麼進程就會自動結束。守護線程通常會被用來做日誌,性能統計等工作。

 

4. 優先級

Java 給每個線程指定一個優先級。默認情況下,線程繼承生成它的線程的優先級。可以用 setPriority 方法提高、降低線程的優先級,還能用 getPriority 方法獲取線程的優先級。

優先級是從1 到10 的數字。

Thread 類有int 型常量 MIN_PRIORITYNORM_PRIORITYMAX_PRIORITY ,分別代表1 、5 和10 。

主線程的優先級是 Thread.NORM_PRIORITY。

例如在上節的歷程裏修改一下程序:

		thread1.start();
		thread2.start();
		thread3.start();
		
		thread3.setPriority(Thread.MAX_PRIORITY);
	}

則任務print100 的線程首先結束。
 

5. 線程池

顧名思義線程池就是線程的容器。在沒有接觸線程池之前,我們使用線程的時候就去創建一個線程,然後startup()就可以(#1裏提到的)

但是就會有一個問題:
如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因爲頻繁創建線程和銷燬線程需要時間。
  
假設,你現在有一個while n(n=10000)的循環,每一次循環都要啓動一個線程去計算n的因數,這樣頻繁而且大量的創建線程,系統的效率會大幅下降,系統會很快奔潰。
  
線程池就可以幫助我們解決這個問題,他使線程可以重複使用,就是執行完一個任務線程不會被銷燬,而是可以繼續執行其他任務

線程池類ThreadPoolExecutor 在包 java.util.concurrent 下

線程池是管理併發執行任務個數的理想方法。 Java 提供 Executor ( n. 執行者 ) 接口來執行線程池中的任務,提供 ExecutorService 接口來管理和控制任務。ExecutorService 是Executor 的子接口
在這裏插入圖片描述
爲了創建一個Executor 對象,可以使用Executors 類中的靜態方法,newFixedThreadPool(int) 方法在池中創建固定數目的線程。
ExecutorService executorService= Executors.newFixedThreadPool(5);

接下來向線程池提交一個任務。
executorService.execute(new TestRunnable());

如果線程完成了任務的執行,它可以被重新使用以執行另外一個任務

如果線程池中所有的線程都不是處於空閒狀態,而且有任務在等待執行,那麼在關閉之前,如果由於一個錯誤終止了一個線程,就會創建一個新線程來替代它。如果線程池中所有的線程都不是處於空閒狀態,而且有任務在等待執行,那麼newCachedThreadPool() 方法就會創建一個新線程。如果緩衝池中的線程在60 秒內都沒有被使用就該終止它。對許多小任務而言,一個緩衝池已經足夠。

Java通過Executors提供四種線程池,分別爲:

newCachedThreadPool——創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

newFixedThreadPool——創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

newScheduledThreadPool——創建一個定長線程池,支持定時及週期性任務執行。

newSingleThreadExecutor——創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

	public static void main(String[] args) {		
		ExecutorService executor = Executors.newFixedThreadPool(3);
		executor.execute(new PrintChar('a',100));
		executor.execute(new PrintChar('b',100));
		executor.execute(new PrintNum(100));
		
		executor.shutdown();
	}

把第2行換成:ExecutorService executor = Executors.newCachedThreadPool();
又會發生什麼呢?

將爲每個等待的任務創建一個新線程,所以,所有的任務都併發地執行。

最後一行的方法shutdown() 通知執行器關閉。不能接受新的任務,但是現有的任務將繼續執行直至完成。

如果僅需要爲一個任務創建一個線程,就使用Thread 類。如果需要爲多個任務創建線程,最好使用線程池。

這是一位大佬寫的關於線程池使用的文章,日後詳看!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章