線程池

·爲什麼要使用線程池?
使用線程池是爲了節省資源,如果每次有任務要來執行,每次都重新創建一個線程,使用完之後再銷燬,創建和銷燬線程池的代價是很大的,這很划算不來;所以才引進了線程池;
且使用了線程池後,如果當有任務進來時,如果此時線程池內有空閒線程可以來處理該任務,那麼就可以直接執行該任務,不用再等待創建線程池了,還提高了效率;

·線程池的使用
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
通過該構造函數來創建線程池;

(1)corePoolSize:核心池的大小;

(2)maximumPoolSize:線程池最大線程數量;
corePoolSize和maximumPoolSize的數量大小,可以相等也可以不相等;

(3)keepAliveTime:線程保持活動時間,即線程池的工作線程空閒後,保持存活的時間;
(4)TimeUnit :keepAliveTime的時間單位

(5)workQueue(工作隊列):用於保存等待執行任務的阻塞隊列。
可以選擇以下幾個阻塞隊列:
A.ArrayBlockingQueue:基於數組結構的有界阻塞隊列,此隊按照FIFO原則對元素進行排序;

B.LinkBlockingQueue:基於鏈表結構的阻塞隊列,鏈表是沒有數量限制的,按照FIFO排序元素,吞吐量高於ArrayBlockingQueue;

C.SynchronousQueue:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞隊列,通常吞吐量比LinkedBlockingQueue還要高;

D.PrioityBlockingQueue:具有優先級的無界阻塞隊列

(6)RejectedExecutionHandler (飽和策略):當隊列和線程都滿了,線程池會採用什麼手段來處理新任務,默認採用AbortPolicy
JDK一共內置4個飽和策略:
A.AbortPolicy:表示無法處理新任務拋出異常,JDK默認採用此策略
B.CallerRunPolicy:等待調用者線程空閒後運行任務;
C.DiscardOldestPolicy:丟棄阻塞隊列中最近的一個任務並執行當前任務;
D.DiscardPolicy:不處理,直接將新任務丟棄,也不報錯;

且一般在創建線程池時,如果不想換飽和策略,RejectedExecutionHandler這一項可以省略不寫,會自動使用默認的飽和策略;

·線程池的執行流程:
當一個Runnable或Callable對象到達線程池時:
第一步:首先判斷核心線程池中線程是否有空閒線程, 如果有空閒線程,則將任務直接分配給空閒線程執行;如果沒有空閒線程,則查看核心線程池中線程數量是否已經達corePoolSize的大小了,如果線程數量沒達到corePoolSize,則創建線程執行任務;如果線程數量達到corePoolSize了,則執行第二步;

第二步:判斷工作隊列(BlockingQueue)是否已滿,如果工作隊列沒有滿,將提交任務存儲到工作隊列中等待覈心池的調度;如果工作隊列也滿了,則執行第三步;

第三步:判斷當前線程池中的線程數是否已達到了最大值maxiumSize,
若已達到最大值maxiumSize,將任務交給飽和策略處理;
否則,繼續創建新線程執行此任務。

·線程池有兩種提交任務的方式:
execute和submit;
execute由於提交沒有返回值的任務;
submit用於提交有返回值的任務;

用execute來提交任務: Runnable是沒有返回值的,可以用execute來提交

import java.util.concurrent.*;
class MyThread implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}
public class Test{
    public static void main(String[] args) {
        ExecutorService executorService=new ThreadPoolExecutor(3,5,
                2000,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
       MyThread myThread=new MyThread();
        for(int i=0;i<5;i++){
            executorService.execute(myThread);
        }
        executorService.shutdown();
    }
}

因爲採用的工作隊列是 LinkedBlockingQueue,鏈表中存放數據沒有大小限制,所以當核心線程池滿時,進來的任務就會進入到阻塞對列中,而阻塞隊列中存放任務數量沒有限制,所以創建線程池不會超過核心線程池的大小;通過運行結果也可以看出:
在這裏插入圖片描述

**用submit來提交任務:**結果可以用Future來接收,使用get方法來獲取返回值。Callable是有返回值的,可以用submit來提交

import java.util.concurrent.*;
class YourThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        for(int i=0;i<6;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
        return Thread.currentThread().getName()+"執行完畢";
    }
}
public class Test {
   public static void main(String[] args) {
        ExecutorService executorService=new ThreadPoolExecutor(3,5,
                2000,TimeUnit.MILLISECONDS,new  LinkedBlockingQueue<>());
        YourThread myThread=new YourThread();
        for(int i=0;i<5;i++){
            executorService.submit(myThread);
            //或者可以使用Future 來接收,使用get方法來獲取返回值
          /*  Future future=executorService.submit(myThread);//用Future.get來接收返回值與 不用Future.get來接收返回值,結果運行有很大差別
            try {
                System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }*/
        }
        executorService.shutdown();
    }
}

未使用Futute.get()來接受返回值:
在這裏插入圖片描述

使用Futute.get()來接受返回值:

在這裏插入圖片描述
結果用不用Future.get()來接收返回值,運行的結果有很大差異;原因是:Future.get()會阻塞其他線程,一直等到當前Callable線程執行完畢拿到返回值爲止(是調用了Future的get()方法;如果只是使用了Future來接收,但並沒有使用它的get()方法來接收返回值,則不會出現阻塞的現象)。

使用完線程池要記得關閉:shutdown;

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