【Java多線程】第一個線程池程序

線程池它就是一個池子(就像是養魚的池子),可以養一定數量的魚,可以重複使用!學習Java中的線程池,就是學習Java用了什麼工具(API)和方法(設計模式)來搞出可以“養魚的池子”。

本文作爲入門級的線程池教程,主要介紹第一個線程池的一般寫法,也就是“Hello,world”的水平,快速入門!


Table of Contents

什麼是線程池

線程池體系

第一個線程池


什麼是線程池

背景:

  1. 如果反覆創建銷燬線程,也會有一定的系統資源開銷
  2. 線程池,其實就是一個容納多個線程的容器;Java初始時提供一定數量的、可重複使用的線程;
  3. 去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。
  4. 此外,當我們線程創建過多時,容易引發內存溢出,因此我們就有必要使用線程池的技術了。

爲什麼要使用線程池?

  1. 在java中,如果每個請求到達就創建一個新線程開銷是相當的。
  2. 在實際使用中,創建和銷燬線程花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創建和銷燬線程的開銷之外,活動的線程也需要消耗系統資源。
  3. 如果在一個jvm裏創建太多的線程,可能會使系統由於過度消耗內存或“切換過度”而導致系統資源不足
  4. 爲了防止資源不足,需要採取一些辦法來限制任何給定時刻處理的請求數目,儘可能減少創建和銷燬線程的次數,特別是一些資源耗費比較大的線程的創建和銷燬,儘量利用已有對象來進行服務。
  5. 線程池主要用來解決線程生命週期開銷問題資源不足問題
  6. 通過對多個任務重複使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由於在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。
  7. 這樣,就可以立即爲請求服務,使用應用程序響應更快。
  8. 另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。

《Java 併發編程的藝術》提到的來說一下使用線程池的好處:

  1. 降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
  2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  3. 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

注意:

  1. 程序一開始的時候,創建多個線程對象,存儲到集合中;當需要線程的時候,從幾回閤中獲取線程出來;
  2. 從JDK1.5開始,程序員不再需要自己開發線程池,而是使用內置線程池技術;

線程池體系

JDK給我們提供了Excutor框架來使用線程池,它是線程池的基礎。Executor提供了一種將“任務提交”與“任務執行”分離開來的機制(解耦)。


第一個線程池

線程池的使用流程:

  1. 調用Executors類的靜態方法創建線程池;
  2. 調用submit提交Runnable或Callable對象;
  3. 保存好返回的Future對象,以便得到結果或者取消任務;
  4. 當不想再提交任何任務時,調用shutdown

向線程池中提交Runnable接口的線程

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo {
	public static void main(String[] args) {
		
		//第一步:調用Executors類的靜態方法創建線程池;
		ExecutorService es = Executors.newFixedThreadPool(5); //創建一個固定大小的線程池
		
		//第二步-1:Runnable或Callable對象
		MyTask task = new MyTask();
		
		//第二步-2:提交Runnable或Callable對象到線程池中
		for(int i=0; i<10; i++) {
			es.submit(task); //連續提交10個線程到線程池中
		}
		es.shutdown();
	}
}
class MyTask implements Runnable {
	@Override
	public void run() {
		System.out.println("時間:"+System.currentTimeMillis()+";線程執行ID:"+
					Thread.currentThread().getName());
		try {
			Thread.sleep(1000); //休眠1s
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 程序輸出:

  1. 時間:1585382666365;線程執行IDpool-1-thread-1
  2. 時間:1585382666365;線程執行IDpool-1-thread-4
  3. 時間:1585382666365;線程執行IDpool-1-thread-5
  4. 時間:1585382666365;線程執行IDpool-1-thread-2
  5. 時間:1585382666365;線程執行IDpool-1-thread-3

 

  1. 時間:1585382667366;線程執行IDpool-1-thread-5
  2. 時間:1585382667367;線程執行IDpool-1-thread-1
  3. 時間:1585382667367;線程執行IDpool-1-thread-2
  4. 時間:1585382667367;線程執行IDpool-1-thread-3
  5. 時間:1585382667367;線程執行IDpool-1-thread-4

分析:

  1. 根據輸出:前5個任務和後5個任務的執行時間正好相差1s!
  2. 並且,前5個和後5個任務的線程ID都是完全一致的!說明前5個線程是同一個整體,後5個線程又是另一個整體!

 

向線程池中提交Callable接口的線程

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadDemo {
	public static void main(String[] args) {
		
		//第一步:調用Executors類的靜態方法創建線程池;
		ExecutorService es = Executors.newFixedThreadPool(2); //創建一個固定大小的線程池
		
		//第二步-1:創建Runnable或Callable對象
		MyTask task = new MyTask();
		
		//第二步-2:提交Runnable或Callable對象到線程池中
		//第三步:用Future保存好每個任務,以便對任務的操作
		Future<String> re01 = es.submit(task);
		Future<String> re02 = es.submit(task);
		
		try {
			System.out.println(re01.get()+re02.get()); //對任務進行操作!
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
		//第四步:關閉線程池
		es.shutdown();
	}
}

class MyTask implements Callable<String> {
	public String call() {
		String info = Thread.currentThread().getName();
		return info+"執行了....";
	}
}

Callable接口和Runnable接口一樣,都是Java中多線程的實現方式。其中Callable接口實現的線程可以拋出異常,也可以有返回值,而Runnable接口實現的線程則不可以(不可拋異常,不可有返回值)!

 

 

 

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