多線程
多線程的意義
多線程是為了同步完成多項任務,不是為了提高運作效率(強調!),而是為了提高資源使用效率來提高系統的效率。
多線程的實作方法
繼承Thread類和實作Runnable接口
繼承Thread類有一個缺點就是單繼承,而實作Runnable接口則彌補了它的缺點,可以實作多重繼承(不是多繼承!不是多繼承!不是多繼承!)。
繼承Thread類必須如果産生Runnable執行個體對象,就必須産生多個Runnable執行個體對象,然後再用Thread産生多個線程;而實作Runnable接口,隻需要建立一個實作這個類的執行個體,然後用這一個執行個體對象産生多個線程。即實作了資源的共享性
下面簡單說一下這部分的實作。
public class ThreadsDemo0 {
//跟建立線程一樣,隻不過建立了之後多次調用而已
public static void main(String[] args) {
//實作Runnable接口
Thread002 t1 = new Thread002("1");
Thread002 t2 = new Thread002("2");
t1.start();
t2.start();
//繼承Thread
Threads001 td = new Threads001();
Thread ts1 = new Thread(td,"1号");
Thread ts2 = new Thread(td,"2号");
ts1.start();
ts2.start();
}
static class Threads001 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"實作runnable接口線程運作");
}
}
static class Thread002 extends Thread{
public Thread002(String num){
super(num);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"号繼承Thread方法運作");
}
}
}
線程池
線程池是一種多線程處理形式,處理過程中将任務添加到隊列,然後在建立線程後自動啟動這些任務。簡單來說就相當于把寶貴的資源放到一個池子中;每次使用都從裡面擷取,用完之後又放回池子供其他人使用。
一般建立線程池,阿裡開發手冊上是推薦使用ThreadPoolExecutor來建立線程池,這樣能夠展現出線程池的參數,便于他人能詳細了解建立的線程池的屬性。
jdk8的文檔ThreadPoolExecutor類中可以看出是提供了四種構造方法。
我們這裡選擇最多參數的那個進行解析
corePoolSize - 核心線程數,即使空閑時仍保留在池中的線程數,除非設定 allowCoreThreadTimeOut
maximumPoolSize - 池中允許的最大線程數,核心線程+臨時線程數
keepAliveTime - 臨時線程執行完任務時,在終止前等待新任務的最大時間。
unit - keepAliveTime參數的時間機關
workQueue -線程的 隊列政策
threadFactory - 執行程式建立新線程時使用的工廠
handler - 線程池超過maximumTime,并且workqueue已經打滿時或線程已關閉時執行的拒絕政策
線程池創的流程大緻如此,需要注意幾點
1、線程池建立時不會立即建立線程,隻有首次任務送出後,才會建立線程。
2、如果有新任務,目前線程數大于corePoolSize且目前線程數小于MaximumPoolSize時,優先調用閑置線程,若沒有則開啟新線程
3、若目前線程大于maximunPoolSize,執行拒絕政策
線程的隊列政策:
SynchronousQuene:不緩存任務的阻塞隊列,生産者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被排程執行該任務,如果沒有可用線程,則建立新線程,如果線程數量達到maxPoolSize,則執行拒絕政策。
ArrayBlockingQueue:FIFO,有界 。需要注意的是:當任務持續以平均送出速度大餘平均處理速度時,會導緻隊列無限增長問題,導緻記憶體等資源溢出
LinkedBlockingQueue:基于連結清單,FIFO,無界。 當任務持續以平均送出速度大餘平均處理速度時,會導緻隊列無限增長問題,導緻記憶體等資源溢出
PriorityBlockingQueue:無界阻塞隊列,直到系統資源耗盡。預設情況下元素采用自然順序升序排列。也可以自定義類實作compareTo()方法來指定元素排序規則,或者初始化PriorityBlockingQueue時,指定構造參數Comparator來對元素進行排序。
線程池的拒絕政策:(1. 線程池已經被關閉;2. 任務隊列已滿且maximumPoolSizes已滿)
SynchronousQueue 無緩沖同步隊列,直接送出,每個插入操作必須等待另一個線程相應的删除操作,反之亦然。适合運作在一個線程中的對象必須與在另一個線程中運作的對象同步,以便傳遞一些資訊,事件或任務
LinkedBlockingQueue 可選擇性無界阻塞隊列,可通過構造函數來進行設定(見補充),新元素插入隊列的尾部,隊列檢索操作擷取隊列頭部的元素。連結隊列通常具有比基于陣列的隊列更高的吞吐量,但在大多數并發應用程式中的可預測性能較低
PriorityBlockingQueue 無界隊列 它沒有限制,在記憶體允許的情況下可以無限添加元素;它又是具有優先級的隊列,是通過構造函數傳入的對象來判斷,傳入的對象必須實作comparable接口
ArrayBlockingQueue 有界阻塞隊列 有助于防止系統資源耗盡,但隊列大小和maximumPoolSize需要互相折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、作業系統資源和上下文切換開銷,但是可能導緻人工降低吞吐量
線程池的使用
能夠快捷使用的五種線程池
newSingleThreadExecutor:核心線程與最大線程都是隻有一個,其他的參數已經沒有什麼效果了。一個單線程的線程池,可以用于需要保證順序執行的場景,并且隻有一個線程在執行。
newFixedThreadPool:一個固定大小的線程池,可以用于已知并發壓力的情況下,對線程數做限制。
newCachedThreadPool:核心線程為0,臨時線程無限(integer的最大值),線程運作完畢後,60s的存活時間 一個可以無限擴大的線程池,比較适合處理執行時間比較小,各自相對獨立的任務。
newScheduledThreadPool:輸入核心線程數,隊列無限大(integer的最大值),DelatWorkQueue保證了可以延時啟動,定時啟動的線程池,适用于需要多個背景線程執行周期任務的場景。
newWorkStealingPool:一個擁有多個任務隊列的線程池,可以減少連接配接數,建立目前可用cpu數量的線程來并行執行,适合使用在很耗時的操作,但是newWorkStealingPool不是ThreadPoolExecutor的擴充,它是新的線程池類ForkJoinPool的擴充,但是都是在統一的一個Executors類中實作(這部分跟其他幾個線程有些許差別,大緻就是将任務按照工作線程均分。然後先工作完的線程去幫助沒處理完的線程工作。以實作最快完成工作,具體實作代碼示例
)
然後就是一般線程池的使用(推薦)
雖然有已經定義好的幾個線程池供人使用,但是在阿裡開發手冊中,還是推薦大家使用這種自己定義好的方式來使用線程,一來可以很明顯的看到各個參數,了解目前這個線程池的基本屬性;二來更容易了解線程池的運作規則,避免資源耗盡的風險。當然具體情況還是看實際開發情況,不推薦但是并不是禁止使用(存在即合理嘛)