天天看點

Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

ThreadPool線程池

  • 1.線程池的優勢
    • 1.1.引言
    • 1.2.為什麼要使用線程池
  • 2.線程池的使用
    • 2.1.架構說明
    • 2.2.線程池的三大方法
      • 2.2.1.newFixedThreadPool(int)方法
      • 2.2.2.newSingleThreadExector
      • 2.2.3.newCachedThreadPool
  • 3.ThreadPoolExecutor底層原理
  • 4.線程池7大重要參數

1.線程池的優勢

1.1.引言

與資料庫線程池類似,如果沒有資料庫連接配接池,那麼每次對資料庫的連接配接池都要new來擷取連接配接池。重複的連接配接和釋放操作會消費大量的系統資源,我們可以使用資料庫連接配接池,直接去池中取連接配接池。

同樣,在沒有線程池之前,我們也是通過new Thread.start()來擷取線程,現在我們也不需要new了,這樣就能實作複用,使得我們系統變得更加高效。

1.2.為什麼要使用線程池

例子:

  • 10年前單核CPU電腦,假的多線程,像馬戲團小醜玩多個球,CPU需要來回切換。
  • 現在是多核電腦,多個線程各自跑在獨立的CPU上,不用切換效率高。

線程池的優勢:

線程池做的工作隻要是控制運作的線程數量,處理過程中将任務放入隊列,然後線上程建立後啟動這些任務,如果線程數量超過了最大數量,超出數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行。

它的主要特點為:

  • 線程複用
  • 控制最大并發數
  • 管理線程

優點:

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

2.線程池的使用

2.1.架構說明

Executor 架構是什麼?

Java Doc中是這麼描述的

An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads.

執行送出的Runnable任務的對象。這個接口提供了一種将任務送出與如何運作每個任務的機制,包括線程的詳細資訊使用、排程等。通常使用Executor而不是顯式地建立線程。

Java中的線程池是通過Executor架構實作的,,該架構中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類。而我們常用的接口是ExecutorService子接口,Executors是線程的工具類(類似數組的工具類Arrays,集合的工具類Collections)。ThreadPoolExecutor是這些類的重點。我們可以通過輔助工具類Executors拿到ThreadPoolExecutor線程池

Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

各個類更加詳細的介紹如下:

Executor所有線程池的接口,隻有一個方法,該接口定義執行Runnable任務的方式

ExecutorService 增加Executor的行為,是Executor實作類的最直接的接口,該接口定義提供對Executor的服務

Executors 線程池工廠類,提供了一系列工廠方法用于建立線程池,傳回的線程池都實作了

ScheduledExecutorService:定時排程接口。

AbstractExecutorService 執行架構抽象類。

ThreadPoolExecutor JDK中線程池的具體實作,一般用的各種線程池都是基于這個類實作的

2.2.線程池的三大方法

2.2.1.newFixedThreadPool(int)方法

Exectors.newFixedThreadPool(int) -->執行長期任務性能好,建立一個線程池,一池有N個固定的線程,有固定線程數的線程

public static void main(String[] args) {
		//一池5個受理線程,類似一個銀行5個受理視窗。不管你現在多少個線程,都隻有5個
		ExecutorService threadPool=Executors.newFixedThreadPool(5); 
		
		try {
			//模拟有10個顧客過來銀行辦理業務,目前池子裡面有5個從業人員提供服務。
			for(int i=1;i<=10;i++){
				//execute方法裡面有一個參數,參數類型是Runnable,Runnable是函數式接口,可以用lambda表達式,并且runnable就是這10個顧客
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
				});
			}
		} catch (Exception e) {
			// TODO: handle exception
		}finally{
			threadPool.shutdown();
		}	
	}
           
Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

可以看到執行結果。池子中有5個線程,相當于5個從業人員對外提供服務,辦理業務。圖中1号視窗辦理了兩次業務,銀行的受理視窗可以多次被複用。也不一定是每個人辦理兩次,而是誰辦理的快誰就辦理的多。

當我們再線程執行的過程中加400ms的延遲,可以看看效果

public static void main(String[] args) {
		//一池5個受理線程,類似一個銀行5個受理視窗。不管你現在多少個線程,都隻有5個
		ExecutorService threadPool=Executors.newFixedThreadPool(5); 
		
		try {
			//模拟有10個顧客過來銀行辦理業務,目前池子裡面有5個從業人員提供服務。
			for(int i=1;i<=10;i++){
				//execute方法裡面有一個參數,參數類型是Runnable,Runnable是函數式接口,可以用lambda表達式,并且runnable就是這10個顧客
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
				});
				try {
					TimeUnit.MILLISECONDS.sleep(400);
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
		}finally{
			threadPool.shutdown();
		}	
	}
           
Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

此時說明網絡擁堵的情況下或者辦理業務比較慢,則線程池辦理業務任務配置設定情況比較平均。

2.2.2.newSingleThreadExector

Exectors.newSingleThreadExector()–>一個任務一個任務的執行,一池一線程

public static void main(String[] args) {
	//一池一個工作線程,類似一個銀行有1個受理視窗
	ExecutorService threadPool=Executors.newSingleThreadExecutor(); 	
	try {
		//模拟有10個顧客過來銀行辦理業務
		for(int i=1;i<=10;i++){
			//execute方法裡面有一個參數,參數類型是Runnable,Runnable是函數式接口,可以用lambda表達式,并且runnable就是這10個顧客
			threadPool.execute(()->{
				System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
			});
		}
	} catch (Exception e) {
		// TODO: handle exception
	}finally{
		threadPool.shutdown();
	}	
}
           
Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

2.2.3.newCachedThreadPool

Exectors.newCachedThreadPool()–>執行很多短期異步任務,線程池根據需要建立新線程,但在先前建構的線程可用時将重用他們。可擴容,遇強則強。一池n線程,可擴容,可伸縮,cache緩存的意思

那麼池的數量應該設定多少呢,如果銀行隻有一個視窗,那麼當人來得太多了,就忙不過來。如果銀行有很多個視窗,但是人來的少,此時又顯得浪費資源。那麼如何該合理安排呢?這就需要用到newCachedThreadPool()方法,可擴容,可伸縮

public static void main(String[] args) {
		//一池一個工作線程,類似一個銀行有n個受理視窗
		ExecutorService threadPool=Executors.newCachedThreadPool(); 	
		try {
			//模拟有10個顧客過來銀行辦理業務
			for(int i=1;i<=10;i++){
				//execute方法裡面有一個參數,參數類型是Runnable,Runnable是函數式接口,可以用lambda表達式,并且runnable就是這10個顧客
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
				});
			}
		} catch (Exception e) {
			// TODO: handle exception
		}finally{
			threadPool.shutdown();
		}	
	}
           
Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數
public static void main(String[] args) {
		//一池一個工作線程,類似一個銀行有n個受理視窗
		ExecutorService threadPool=Executors.newCachedThreadPool(); 	
		try {
			//模拟有10個顧客過來銀行辦理業務
			for(int i=1;i<=10;i++){
				try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedException e){e.printStackTrace();}
				//execute方法裡面有一個參數,參數類型是Runnable,Runnable是函數式接口,可以用lambda表達式,并且runnable就是這10個顧客
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
				});
			}
		} catch (Exception e) {
			// TODO: handle exception
		}finally{
			threadPool.shutdown();
		}	
	}
           
Java并發程式設計-ThreadPool線程池1.線程池的優勢2.線程池的使用3.ThreadPoolExecutor底層原理4.線程池7大重要參數

3.ThreadPoolExecutor底層原理

newFixedThreadPool底層源代碼

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
           

可以看到,底層的參數包含LinkedBlockingQueue阻塞隊列。

newSingleThreadExecutor底層源代碼

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
           

newCachedThreadPool底層源代碼

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
           

SynchronousQueue這個阻塞隊列是單一版阻塞隊列,阻塞隊列的容量為1.

這3個方法其實都共同傳回了一個對象,即ThreadPoolExecutor的對象。

4.線程池7大重要參數

ThreadPoolExecutor的構造函數

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
           

上面的int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,

RejectedExecutionHandler handler即我們的七大線程參數

上面是ThreadPoolExecutor類的構造方法,有7大參數:

1)corePoolSize:線程池中的常駐核心線程數,簡稱核心數。

比如說,一個線程池我們可以把它當作銀行的網點,銀行隻要開門,就必須至少有一個人在值班,這個就叫常駐核心線程數。比如如果某個銀行周一到周五五個網點全開,那麼周一到周五的常駐核心線程數為5.如果今天業務沒有那麼頻繁,視窗為1,那麼今天的常駐核心線程數就是1

2)maxImumPoolSize:線程池中能夠容納同時執行的最大線程數,此值必須大于等于1

3)keepAliveTime:多餘的空閑線程的存活時間,目前池中線程數量超過corePoolSize時,當空閑時間達到keepAliveTime時,多餘線程會被銷毀直到剩下corePoolSize為止

如果線程池中有常駐線程數,又有最大線程數,說明平時是用常駐的,工作緊張了,它會擴容到最大線程數,如果業務降下來了,我們設定了多餘的空閑線程的存活時間,比如設定30s,如果30s都沒有多餘的請求過來,有些銀行就會關閉視窗,是以它不僅會擴大還會縮小。

4)unit:keepAliveTime的機關

機關:是秒,毫秒,微秒。

5)workQueue:任務隊列,被送出但尚未被執行的任務

這是一個阻塞隊列,比如說銀行,隻有3個受理視窗,而來了4個客戶。這個阻塞隊列就是銀行的候客區,來了客戶不能讓他走了。視窗數控制了線程的并發數。

6)threadFactory:表示生成線程池中工作線程的線程工廠,用于建立線程,一般預設即可

線程都是統一的建立。線程池裡面有已經new好的線程,這些由線程池工廠生産。

7)handler:拒絕政策,表示目前隊列滿了,并且工作線程大于等于線程池的最大線程數(maximumPoolSize)時如何來拒絕請求執行的runnable的政策

比如說今天銀行客流高峰,三個視窗都滿了,候客區也滿了。我們沒有選擇繼續拉人,因為不安全我們選擇委婉的拒絕。

在下一節我們将介紹線程池底層工作原理