線程池,你知多少【java線程高併發提升三】

上文鏈接:鎖的智慧【java線程高併發提升二】


1、線程池是個什麼東西?

線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啓動這些任務。線程池線程都是後臺線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。如果某個線程在託管代碼中空閒(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間後創建另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成後才啓動。

2、線程池的五種狀態?

廢話不說,直接上代碼(代碼在RejectedExecutionHandler -》rejectedExecution -》DiscardPolicy中)。

// runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
狀態 介紹
RUNNING 運行狀態
SHUTDOWN 關閉狀態,此狀態下線程池不在接收新任務,只處理已添加任務。
STOP 停止狀態,此狀態下線程池停止所有任務。
TIDYING 整體樁體,此狀態下線程池中任務數爲0
TERMINATED 終結狀態

各個狀態轉換如下:
在這裏插入圖片描述

3、線程池爲什麼這麼創建、參數幾何?

線程池創建過程如下:

package Three_ThreadFactory;

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

public class createFactory {

	public static void main(String[] args) {
		//線程池創建方式
		ExecutorService theadpool = Executors.newFixedThreadPool(1);
		theadpool.execute(()->{
			System.out.println("當前線程名字是:" + Thread.currentThread().getName());
		});
	}
}

接下來我們要開始講述源碼了!!!
newFixedThreadPool : 固定線程池,其參數中可以設置線程數量,比如上面設置了1。定位到源碼:

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

該方法返回一個ThreadPoolExecutor對象。

ThreadPoolExecutor:線程池程序。定位到源碼:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

ThreadPoolExecutor可以設置參數及作用如下(相應源碼可以自行定位查看):

參數名 作用
corePoolSize 核心線程數量
maximumPoolSize 最大可容載線程數量
keepAliveTime 線程中空閒線程最長不釋放時間(0表示一直不釋放)
workQueue 存放等待執行的任務
threadFactory 線程池創建工廠,用於創建具體線程,默認實現爲:defaultThreadFactory()
defaultHandler 當workQuery無法新加任務時,提供拒絕策略,默認拒絕策略:AbortPolicy(拋出異常)

defaultHandler:處理策略,在線程池程序調用execute後通過reject(command)可以設置相應拒絕策略。

4、線程池執行機制真是這樣的!

//執行過程
	public static void main(String[] args) {
		//
		ExecutorService theadpool = Executors.newFixedThreadPool(1);
		theadpool.execute(()->{
			System.out.println("當前線程名字是:" + Thread.currentThread().getName());
		});
	}
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

對以上源碼的描述:調用execute方法後,檢查線程池的運行狀態和工作線程數量,如果工作線程的線程數少於核心線程數,則會創建一個新的線程來執行給定的任務,調用addWorker來執行任務,如果線程池線程數量超過核心線程數量,把任務放到工作任務隊列中,若工作隊列未溢出,則添加任務,添加時重新檢查線程池狀態,如果沒有繼續運行(不是RUNNING狀態)就把任務移除,使用拒絕策略來處理當前任務;否則將創建或喚醒工作線程來執行任務(線程池非RUNNING狀態或添加任務失敗後,使用拒絕策略來處理當前任務)。

5、線程池中的LinkedBlockingQueue阻塞隊列。

(1) 接口及類繼承展示。在這裏插入圖片描述
(2)屬性講述

名稱 類型 含義
capacity int 隊列長度
count AtomicInteger 隊列元素數量
head Node 頭結點
last Node 尾節點
takeLock ReentrantLock 取出節點鎖
notEmpty Condition 用以喚醒取出某節點的線程
putLock ReentranLock 添加節點的鎖
notFull Condition 用以喚醒某節點的線程

(3)隊列添加、取出元素

//阻塞隊列的使用
package Three_ThreadFactory;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class LinkQueue {
	
	  
	public static void main(String[] args) {
		BlockingQueue queue = new LinkedBlockingQueue(5);//聲明長度爲5的阻塞隊列
		String s = "我要進隊列";
		queue.put(s);
		queue.take();
		//...省略
	}
	
}

方法總覽:

方法 作用 介紹
boolean add(E e) 添加元素 底層調用offer(E e)
boolean offer(E e) 添加元素 隊列滿則返回失敗,否則獲取鎖去添加元素
boolean affer(E e,long timeout,TimeUnit unit) 添加元素,超時返回 隊列未滿等同offer;隊列已滿則等待指定時間,錯誤則返回
boolean put(E e) 阻塞地添加元素 阻塞等待直到完成添加元素
E peek() 取出元素僅 僅獲取元素不刪除
E pool() 取出元素,並刪除隊列中該元素
E pool(long timeout,timeUnit unit) 未超時前,取出元素,並刪除隊列中該元素 隊列不爲空,與pool相同,隊列空則等待制定時間否則返回空
E take() 阻塞地取出元素 一直阻塞直到取出元素

6、線程中的ScheduledExecutorService預執行服務

該類應用於定時執行異步任務、週期性執行異步任務,需要注意異常處理,當拋出異常時,任務將終止週期性執行。

7、java中常用的線程池。

(1)固定線程數量的線程池

//固定線程線程池創建
public static void fixThreadpool() {
	ExecutorService theadpool = Executors.newFixedThreadPool(1);
	theadpool.execute(()->{
		System.out.println("當前線程名字是:" + Thread.currentThread().getName());
	});
}
//newFixedThreadPool源碼
 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

Executors.newFixedThreadPool創建固定線程線程池,源碼中核心線程數和可容載最大線程數相同,當達到核心線程數後,空閒線程不會因超時而被終止或釋放,且每添加一個任務後,會將任務添加到工作任務隊列,線程池創建一個線程,線程數等於核心線程數時,不會再創建線程。

(2)單線程的線程池

//單線程線程池創建
public static void singleThreadpool() {
	ExecutorService theadpool = Executors.newSingleThreadExecutor();
	theadpool.execute(()->{
		System.out.println("當前線程名字是:" + Thread.currentThread().getName());
	});
}
//源碼
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

通過Executors.newSingleThreadExecutor創建,由源碼可知核心線程數和最大線程數一樣,且均爲1,即只有一個線程在執行隊列的工作任務,注意該方法使用了FinalizableDelegatedExecutorService代理來創建線程池。

(3)可緩存的線程池

//可緩存線程線程池創建
public static void cacheThreadpool() {
	ExecutorService theadpool = Executors.newCachedThreadPool();
	theadpool.execute(()->{
		System.out.println("當前線程名字是:" + Thread.currentThread().getName());
	});
}
//源碼
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

import java.lang.Integer
//@Native public static final int   MAX_VALUE = 0x7fffffff;

Executors.newCachedThreadPool創建可緩存的線程池,核心線程數爲0,最大線程數爲無限大(上面源碼課件),空閒線程可以超時存在60s,使用SynchronousQueue同步隊列(在添加任務時需同步有另一個工作線程來使用這個任務,否則無法添加)。
(4)定時執行的線程池

//定時執行線程線程池創建
public static void timeThreadpool() {
	ExecutorService theadpool = Executors.newScheduledThreadPool(1);
	theadpool.execute(()->{
		System.out.println("當前線程名字是:" + Thread.currentThread().getName());
	});
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
       super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
             new DelayedWorkQueue());
   }

Executors.newScheduledThreadPool創建定時線程,可指定核心線程數,最大線程數爲無限大,核心線程數空閒不會超時回收,使用了DelayedWorkQueue延時隊列,通過延時隊列控制時間執行線程。

8、機票系統多線程+Future運行實例

package Three_ThreadFactory;

import java.util.ArrayList;
import java.util.Random;
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;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class imiTicketExample {

	public static void main(String[] args) {
		//創建核心線程數等同於主機處理器數量的線程池
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
		
		int taskNum = 15;//模擬查詢多個機票公司的票數
		ArrayList<Future<String>> futuresList = new ArrayList<>();
		
		for (int taskindex = 0; taskindex < taskNum; taskindex++) {
			//通過Callable創建線程
			 Callable<String> curcallable = ()->{
			 	//模擬獲取到機票過程
				long first = System.currentTimeMillis();
				long sleep = new Random().nextInt(2500);
				Thread.sleep(sleep);
				
				//模擬返回獲取到的機票登信息
				return (Thread.currentThread().getName()+"賣出" + (System.currentTimeMillis()-first));//獲取
			};
			
			Future<String> future = newFixedThreadPool.submit(curcallable);
			futuresList.add(future);
		}
		
		//輸出結果 
		for(Future<String> sFuture : futuresList) {
			try {
				String string = sFuture.get(3, TimeUnit.SECONDS);//最多等待三秒獲取
				System.out.println("獲取結果:"+string);
			} catch (InterruptedException | ExecutionException | TimeoutException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
	}
}

在這裏插入圖片描述
上述算法模擬獲取機票售賣公司售賣票的過程,通過Future獲取獲得的信息,因此簡單敘述下Future的運行原理。
Future在主線程阻塞等待時將線程池中的Callable執行結果同步共享變量到FutureTask實現獲取信息。Future使用的場景是:會出現主線程阻塞超時等待的場景,不使用場景:只有一個任務,主線程不會出現阻塞等待的場景。

下文鏈接:兩分鐘瞭解ThreadLocal機制【java線程高併發提升四】

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