基本實現
在java中,Java官方提供了三種方式來幫助我們實現一個線程,其中:
第一種方式:繼承 Thread 對象:extends Thread
// 自定義線程對象
class ApplicationThread extends Thread {
public void run() {
// 線程需要執行的代碼
......
}
}
其中,Thread 類本質上是實現了Runnable 接口的一個實例,代表一個線程的實例。啟動線程的唯一方
法就是通過Thread 類的start()實例方法。start()方法是一個native 方法,它將啟動一個新線程,并執行run()方法。
第二種方式:實現 Runnable 接口(無返回值):implements Runnable
// 實現Runnable接口
class ApplicationThread implements Runnable {
@Override
public void run() {
// 線程需要執行的代碼
......
}
}
其中,如果自己的類已經extends 另一個類,就無法直接extends Thread,此時,可以實現一個Runnable 接口。
第三種方式:實現Callable 接口(有返回值):implements Callable
// 實現Runnable接口
class ApplicationThread implements Callable {
@Override
public void run() {
// 線程需要執行的代碼
......
}
}
其中,執行Callable 任務后,可以獲取一個Future 的對象,在該對象上調用get 就可以獲取到Callable 任務返回的Object對象。
第四種方式:基于線程池方式創建:線程和數據庫連接這些資源都是非常寶貴的資源。那么每次需要的時候創建,不需要的時候銷毀,是非常浪費資源的。那么我們就可以使用緩存的策略,也就是使用線程池。
Java 里面線程池的頂級接口是Executor,但是嚴格意義上講Executor 并不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。
Java主要提供了newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool以及newSingleThreadExecutor 等4種線程池。
目前業界線程池的設計,普遍采用的都是生產者 - 消費者模式。線程池的使用方是生產者,線程池本身是消費者。
Java 并發包里提供的線程池,比較強大且復雜。Java 提供的線程池相關的工具類中,最核心的是 ThreadPoolExecutor,通過名字你也能看出來,它強調的是 Executor,而不是一般意義上的池化資源。
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueueworkQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
對于這些參數的意義,我們可以把線程池類比為一個項目組,而線程就是項目組的成員。其中:
corePoolSize:表示線程池保有的最小線程數。
maximumPoolSize:表示線程池創建的最大線程數。
keepAliveTime & unit:一個線程如果在一段時間內,都沒有執行任務,說明很閑,keepAliveTime 和 unit 就是用來定義這個“一段時間”的參數。也就是說,如果一個線程空閑了keepAliveTime & unit這么久,而且線程池的線程數大于 corePoolSize ,那么這個空閑的線程就要被回收。
workQueue:工作隊列。
threadFactory:通過這個參數你可以自定義如何創建線程名稱。
handler:通過這個參數你可以自定義任務的拒絕策略。
其中,Java在ThreadPoolExecutor 已經提供了以下 4 種策略:
CallerRunsPolicy:提交任務的線程自己去執行該任務
AbortPolicy:默認的拒絕策略,會 throws RejectedExecutionException
DiscardPolicy:直接丟棄任務,沒有任何異常拋出
DiscardOldestPolicy:丟棄最老的任務,其實就是把最早進入工作隊列的任務丟棄,然后把新任務加入到工作隊列
同時, Java 在 1.6 版本還增加了 allowCoreThreadTimeOut(boolean value) 方法,表示可以讓所有線程都支持超時。
調度方式
由于CPU的計算頻率非常高,每秒計算數十億次,因此可以將CPU的時間從毫秒的維度進行分段,每一小段叫作一個CPU時間片。
目前操作系統中主流的線程調度方式是:基于CPU時間片方式進行線程調度。
線程只有得到CPU時間片才能執行指令,處于執行狀態,沒有得到時間片的線程處于就緒狀態,等待系統分配下一個CPU時間片。
由于時間片非常短,在各個線程之間快速地切換,因此表現出來的特征是很多個線程在“同時執行”或者“并發執行”。
在Javs多視程環境中,為了保證所有線程都能按照一定的策略執行,JVM 需要有一個線程調變器支持工作。
這個調度器定義了線程測度的策略,通過特定的機制為多個線分配CPU的使用權,線程調度器中一般包含多種調度策略算法,由這些算法來決定CPU的分配。
除此之外,每個線程還有自己的優先級(比如有高,中、低級別)調度算法會通過這些優先級來實現優先機制。
常見線程的調度模型目前主要分為兩種:(分時)協同式調度模型和搶占式調度模型。
搶占式調度:
系統按照線程優先級分配CPU時間片
優先級高的線程優先分配CPU時間片,如果所有就緒線程的優先級相同,那么會隨機選擇一個,優先級高的線程獲取的CPU時間片相對多一些。
每個或程的執行時間和或候的切換高由調度落控劃,調度器按照某種略為每個線穆分配執行時間,
調度器可能會為每個線整樣分配相的執行時間,也可能為某些特定線程分配較長的執行時間,甚至在極準情況下還可能不給某熱線程分!執行時同片,從而導致某技線相得不到執行,
在搶占式調支機制下,一個線程的堵事不會導致整個進程堵客
(分時)協同式調度:
系統平均分配CPU的時間片,所有線程輪流占用CPU,即在時間片調度的分配上所有線程“人人平等”。
某一線相執行完后會主動通知調度器切換現下一個線程上繼續執行。
在這種模式下,線程的執行時間由線程本身控物,也就是說線程的切換點是可以預先知道的。
在這種模式下,如果某個錢程的邏輯輯存在問題,則可能導致系統運行到一半就阻塞了,最終會導致整個進程阻塞,甚至更糟可能導致整個系統崩潰。
由于目前大部分操作系統都是使用搶占式調度模型進行線程調度,Java的線程管理和調度是委托給操作系統完成的,與之相對應,Java的線程調度也是使用搶占式調度模型,因此Java的線程都有優先級。
主要是 因為Java的線程調度涉及JVM的實現,JVM規范中規定每個線程都有各自的優先級,且優先級越高,則越優先執行。
但是,優先級越高并不代表能獨占執行時間,可能優先級越高得到的執行時間越長,反之,優先級越低的線程得到執行時間越短,但不會出現不分配執行時間的情況。
假如有若干個線程,我們想讓一些線程擁有更多的執行時間或者少分配點執行時間,那么就可以通過設置線程的優先級來實現。
所有處于可執行狀態的線程都在一個隊列中,且每個線程都有自己的優先級,JVM 線程調度器會根據優先級來決定每次的執行時間和執行頻率。
但是,優先級高的線程一定會先執行嗎?我們能否在 Java 程序中通過優先級值的大小來控制線程的執行順序呢?
答案是肯定不能的。主要是因為影響線程優先級語義的因素有很多,具體如下:
不同版本的操作系統和 JVM 都可能會產生不同的行為
優先級對于不同的操作系統調度器來說可能有不同的語義;有些操作系統的調度器不支持優先級
對于操作系統來說,線程的優先級存在“全局”和“本地”之分,不同進程的優先級一般相互獨立
不同的操作系統對優先級定義的值不一樣,Java 只定義了 1~10
操作系統常常會對長時間得不到運行的線程給予增加一定的優先級
操作系統的線程調度器可能會在線程發生等待時有一定的臨時優先級調整策略
JVM 線程調度器的調度策略決定了上層多線程的運行機制,每個線程執行的時間都由它分配管理。
調度器將按照線程優先級對線程的執行時間進行分配,優先級越高得到的 CPU執行時間越長,執行頻率也可能更大。
Java把線程優先級分為10個級別,線程在創建時如果沒有明確聲明優先級,則使用默認優先級。
Java定義了 Thread.MIN_PRIORITY、Thread.NORM PRIORITY和 Thread.MAXPRIORITY這3個常量,分別代表最小優先級值(1)、默認優先級值(5)和最大優先級值(10)。
此外,由于JVM 的實現是以宿主操作系統為基礎的,所以Java各優先級與不同操作系統的原生線程優先級必然存在著某種映射關系,這樣才能夠封裝所有操作系統的優先級來提供統一的優先級語義。
一般情況下,在Linux中可能要與-20~19之間的優先級值進行映射,而Windows系統則有9個優先級要映射。
更多關于“java培訓”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學,課程大綱緊跟企業需求,更科學更嚴謹,每年培養泛IT人才近2萬人。不論你是零基礎還是想提升,都可以找到適合的班型,千鋒教育隨時歡迎你來試聽。