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