大忙人系列面試題_創建線程的方式

一、通過繼承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數目

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