一、通過繼承Thread,並重寫run方法。不使用Thread的缺點,
1:每次new Thread()創建新對象太浪費性能
2:線程缺乏統一的管理,可能會無限的創建線程,相互之間競爭,極有可能佔用過多的系統資源導致死機或OOM;
3:功能不夠豐富(缺少定時、延遲、緩存等)
二、實現Runnable,重寫run方法
三、使用Callable和Future創建線程
1、實現Callable接口的實現類,並重寫call方法,
2、創建Callable實現類的實例,使用FutureTask了,類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法額返回值;
3、使用TutureTask對象作爲Thread對象的target創建並啓動新線程
4、調用FutureTask的get()方法來獲得子線程執行結束後的返回值
public class CallableThreadTest implements Callable<Integer>{
public static void main(String[] args){
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+" 的循環變量i的值"+i);
if(i==20){
new Thread(ft,"有返回值的線程").start();
}
}
try{
System.out.println("子線程的返回值:"+ft.get());
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception{
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
四、利用線程池Executors創建,其中Executors創建有四種方法
1:newSingleThreadExecutor
只能創建一個核心線程和一個最大線程數。並使用LinkedBlockingQueue隊列創建的單項鍊表阻塞隊列。
2:newFixedThreadPool
創建一個定長線程池
創建定長的線程池(核心線程和最大線程數相同),並使用LinkedBlockingQueue隊列創建的單項鍊表阻塞隊列。
3:newCachedThreadPool
創建一個可緩存線程池。
創建的線程最大數爲Int的最大數,20Y,容易導致內存溢出。使用的是異步隊列
4:newScheduledThreadPool
創建一個定長線程池,支持週期性處理事務。
創建線程數最大爲int的最大數,易導致內存溢出。使用DelayedWorkQueue延時隊列,保證添加到隊列中的任務,會按照任務的延時時間進行排序,延時時間少的任務首先被獲取。
我們不使用上述方法的原因是因爲靈活性太低。而且風險太高(容易出內存溢出)。
五、手動創建線程池ThreadPoolExecutor(常用創建線程池方式)
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
參數說明
corePoolSize:線程池核心線程數量,核心線程不會被回收,即使沒有任務執行,也會保持空閒狀態。如果線程池中的線程少於此數目,則在執行任務時創建。
maximumPoolSize:池允許最大的線程數,當線程數量達到corePoolSize,且workQueue隊列塞滿任務了之後,繼續創建線程。
keepAliveTime:超過corePoolSize之後的“臨時線程”的存活時間。
unit:keepAliveTime的單位。
workQueue:當前線程數超過corePoolSize時,新的任務會處在等待狀態,並存在workQueue中,BlockingQueue是一個先進先出的阻塞式隊列實現,底層實現會涉及Java併發的AQS機制。
threadFactory:創建線程的工廠類,通常我們會自定一個threadFactory設置線程的名稱,這樣我們就可以知道線程是由哪個工廠類創建的,可以快速定位。
handler:線程池執行拒絕策略,當線數量達到maximumPoolSize大小,並且workQueue也已經塞滿了任務的情況下,線程池會調用handler拒絕策略來處理請求。
系統默認的拒絕策略有以下幾種
AbortPolicy:爲線程池默認的拒絕策略,該策略直接拋異常處理。
DiscardPolicy:直接拋棄不處理。
DiscardOldestPolicy:丟棄隊列中最老的任務。
CallerRunsPolicy:將任務分配給當前執行execute方法線程來處理。
自定義拒絕策略:實現RejectedExecutionHandler接口,並重寫rejectedExecution即可。
友好的拒絕策略實現有如下:將數據保存到數據庫,待系統空閒時再進行處理
將數據用日誌進行記錄,後由人工處理
案例
如下圖。
線程池corePoolSize=5,線程初始化時不會自動創建線程,所以當有4個任務同時進來時,執行execute方法會新建【4】條線程來執行任務;
前面的4個任務都沒完成,現在又進來2個隊列,會新建【1】條線程來執行任務,這時poolSize=corePoolSize,還剩下1個任務,線程池會將剩下這個任務塞進阻塞隊列中,等待空閒線程執行;
如果前面6個任務還是沒有處理完,這時又同時進來了5個任務,此時還沒有空閒線程來執行新來的任務,所以線程池繼續將這5個任務塞進阻塞隊列,但發現阻塞隊列已經滿了,核心線程也用完了,還剩下1個任務不知道如何是好,於是線程池只能創建【1】條“臨時”線程來執行這個任務了;
這裏創建的線程用“臨時”來描述還是因爲它們不會長期存在於線程池,它們的存活時間爲keepAliveTime,此後線程池會維持最少corePoolSize數量的線程。
線程池線程數設置
如果是CPU密集型應用,則線程池大小設置爲N+1
如果是IO密集型應用,則線程池大小設置爲2N+1
優化IO密集:
最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目
最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目