溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java線程的原理和實(shí)現(xiàn)方式

發(fā)布時(shí)間:2021-06-26 14:49:09 來(lái)源:億速云 閱讀:139 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“java線程的原理和實(shí)現(xiàn)方式”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

多線程的實(shí)現(xiàn)方式以及區(qū)別

我們都知道,實(shí)現(xiàn)多線程的方式是繼承Thread類和實(shí)現(xiàn)Runable接口,那么除了java中不允許多繼承的這個(gè)特性,他們之間還有什么區(qū)別呢?我們看下面這個(gè)例子:

//繼承Thread類
public class ThreadTest extends Thread {
    private AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
       for(int i=0;i<5;i++){
           System.out.println(Thread.currentThread().getName() + " " + count.incrementAndGet());
       }
    }

    public static void main(String[] args) {
        new ThreadTest().start();
        new ThreadTest().start();
    }
}
//實(shí)現(xiàn)Runable接口
public class RunnableTest implements Runnable {
    private AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + count.incrementAndGet());
        }
    }

    public static void main(String[] args) {
        RunnableTest rab=new RunnableTest();
        new Thread(rab).start();
        new Thread(rab).start();
    }
}

通過(guò)觀察console信息我們可以看到,繼承Thread類,無(wú)法共享對(duì)象資源,而實(shí)現(xiàn)Runable則可以;這讓我們可以針對(duì)不同的業(yè)務(wù)情況作出不同的選擇。
java線程的原理和實(shí)現(xiàn)方式

線程的狀態(tài)

Thread.State枚舉中定義了線程的六種狀態(tài)

狀態(tài)說(shuō)明
NEWnew 對(duì)象后線程狀態(tài)
RUNNABLEstart方法調(diào)用后;或者waiting狀態(tài)下的線程調(diào)用了notify、LockSupport.Unpark等方法
BLOCKED等待synchronized代碼塊時(shí)的狀態(tài)
WAITING調(diào)用wait()、join(),LockSupport.park()方法后
TIMED_WAITING調(diào)用wait(long)、sleep(long),join(long)方法后
TERMINATED線程運(yùn)行完畢,或者調(diào)用terminate方法成功

關(guān)于線程狀態(tài)流轉(zhuǎn),看下面這幅圖;左邊是正常狀態(tài)流轉(zhuǎn),右邊是存在block和waiting的情況:
java線程的原理和實(shí)現(xiàn)方式

網(wǎng)上有些文章說(shuō)線程還分為ready和running狀態(tài),這里需要注意的是,這兩種執(zhí)行狀態(tài)都屬于Runable狀態(tài);ready和running狀態(tài)其實(shí)是描述VM/OS是否線程分配CPU資源,JVM并不能決定這一事情。當(dāng)然運(yùn)行中的線程通過(guò)調(diào)用yield方法是可以將running狀態(tài)的線程切換到ready狀態(tài),但這一過(guò)程是OS層面的事情而非JVM層面。

有關(guān)線程的方法

在實(shí)現(xiàn)多線程的過(guò)程中,我們需要用到相關(guān)方法來(lái)進(jìn)行線程狀態(tài)的切換,已達(dá)到不同的目的。

  • Thread類的相關(guān)方法

方法說(shuō)明
start啟動(dòng)線程,使線程進(jìn)入運(yùn)行狀態(tài)
setPriority給線程設(shè)置優(yōu)先級(jí),有三個(gè)可選項(xiàng):Thread.MIN_PRIORITY 最低優(yōu)先級(jí)、Thread.NORM_PRIORITY 普通、Thread.MAX_PRIORITY 最高
sleep使線程進(jìn)入睡眠狀態(tài),需要指定時(shí)間,線程進(jìn)入等待狀態(tài),此時(shí)線程仍然持有鎖
yield使線程交出CPU資源給優(yōu)先級(jí)是同級(jí)及以上的線程,不可以指定時(shí)間,線程仍然是RUNABLE狀態(tài),此時(shí)線程仍然持有鎖
interrupt終止未執(zhí)行的線程,正在執(zhí)行的線程不受影響,被終止的線程進(jìn)入TERMINATED終止?fàn)顟B(tài)
stop強(qiáng)行終止線程,不管線程是否在執(zhí)行中,此方法為廢棄方法,不是線程安全方法,因?yàn)闀?huì)釋放線程持有的鎖導(dǎo)致數(shù)據(jù)不一致的問(wèn)題產(chǎn)生
join等待線程執(zhí)行完畢,一般是在主線程中等待子線程執(zhí)行結(jié)束,此方法會(huì)阻塞當(dāng)前線程
  • Object類的相關(guān)方法
    Obj類主要有wait()和notify()、notifyAll()三個(gè)方法來(lái)控制多線程讀共享數(shù)據(jù)的訪問(wèn);注意這三個(gè)方法不是Thread類的,故只能在同步代碼塊中才會(huì)產(chǎn)生效果。

方法說(shuō)明
wait使線程暫停,并釋放持有的鎖,需要手動(dòng)調(diào)用notify方法喚醒;線程暫停后會(huì)放入等待隊(duì)列
wait(long)同wait,但是可以指定暫停的時(shí)間
notify喚醒暫停的線程,將線程移出等待隊(duì)列;線程喚醒后并不立即執(zhí)行,而是放入獲取鎖的隊(duì)列中等待獲取鎖
notifyAll喚醒所有暫停的線程,將所有線程沖等待隊(duì)列中移出,并放入獲取鎖的隊(duì)列中

線程池

我們?cè)谙到y(tǒng)中直接new線程是可以實(shí)現(xiàn)多線程,但是存在以下弊端:

  • 線程不能重用,過(guò)多的線程創(chuàng)建和銷毀會(huì)降低系統(tǒng)性能

  • 不能控制線程并發(fā)數(shù)量

  • 不能指定線程的定時(shí)執(zhí)行等

ThreadPoolExecutor

ThreadPoolExecutor讓我們可以自定義線程池,它有三個(gè)構(gòu)造函數(shù),它的參數(shù)含義如下 :
java線程的原理和實(shí)現(xiàn)方式

參數(shù)說(shuō)明
int corePoolSize核心線程數(shù)量,當(dāng)線程池內(nèi)的線程數(shù)小于corePoolSize,會(huì)新建線程馬上運(yùn)行任務(wù);這些線程創(chuàng)建后不會(huì)進(jìn)行回收操作,即便是閑置狀態(tài)
int maximumPoolSize最大線程數(shù)量,此參數(shù)需大于corePoolSize;當(dāng)添加任務(wù)時(shí),如果當(dāng)前線程池線程數(shù)量大于corePoolSize,會(huì)提交給等待隊(duì)列,當(dāng)隊(duì)列滿了后,會(huì)新建線程(運(yùn)行線程數(shù)不超過(guò)maximumPoolSize情況下),這一部分線程我們稱之為非核心線程數(shù),它和核心線程沒(méi)有區(qū)別,只是系統(tǒng)會(huì)定時(shí)回收線程,最終線程數(shù)會(huì)保持在corePoolSize數(shù)量
long keepAliveTime非核心線程的回收時(shí)間,默認(rèn)60
TimeUnit unitkeepAliveTime的單位,默認(rèn)秒
BlockingQueue<Runnable> workQueue線程池中的任務(wù)隊(duì)列,提交任務(wù)時(shí)如果核心線程數(shù)達(dá)到,則會(huì)提交到這里排隊(duì)
ThreadFactory threadFactory創(chuàng)建線程的工廠,可以給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置名字。一般情況下無(wú)須設(shè)置該參數(shù)
RejectedExecutionHandler handler拒絕策略,這是當(dāng)任務(wù)隊(duì)列和線程池都滿了時(shí)所采取的應(yīng)對(duì)策略,默認(rèn)是AbordPolicy,表示直接拋出RejectedExecutionException 異常
  • 任務(wù)隊(duì)列

workQueue指明了當(dāng)核心線程數(shù)達(dá)到最大時(shí),任務(wù)的排隊(duì)策略;有三種類型:

參數(shù)說(shuō)明缺點(diǎn)
直接提交例如:SynchronousQueue(默認(rèn)),這是一個(gè)沒(méi)有數(shù)據(jù)緩沖的阻塞隊(duì)列,隊(duì)列中只能存放一個(gè)元素,超出之后后續(xù)線程提交會(huì)阻塞(maximumPoolSize達(dá)到閾值情況下)線程阻塞
無(wú)界隊(duì)列例如:不設(shè)定容量的LinkedBlockingQueue,當(dāng)核心線程數(shù)滿了之后,任務(wù)提交后可以一直存放在隊(duì)列中maximumPoolSize失效,且有OOM風(fēng)險(xiǎn)
有界隊(duì)列例如:ArrayBlockingQueue,當(dāng)核心線程數(shù)滿了之后,一定量的任務(wù)提交可以存放在隊(duì)列中,但是后續(xù)如果添加新的任務(wù),需要有拒絕策略需要指定拒絕策略
  • 拒絕策略

在使用有界隊(duì)列的前提下,如果工作隊(duì)列已滿,我們需要設(shè)定拒絕策略

參數(shù)說(shuō)明
ThreadPoolExecutor.AbortPolicy默認(rèn)策略;直接拋出RejectedExecutionException異常
ThreadPoolExecutor.CallerRunsPolicy將任務(wù)交給主線程執(zhí)行,通過(guò)阻塞主線程達(dá)到減緩提交的作用
ThreadPoolExecutor.DiscardPolicy直接丟棄當(dāng)前任務(wù)
ThreadPoolExecutor.DiscardOldestPolicy丟棄最老的任務(wù)

以上拒絕策略中,除了CallerRunsPolicy其他的都好理解,下面我們使用CallerRunsPolicy策略來(lái)和DiscardPolicy進(jìn)行一個(gè)比對(duì):
java線程的原理和實(shí)現(xiàn)方式
我們定義了最大線程是3個(gè),排隊(duì)兩個(gè),線程睡眠0.5s以達(dá)到效果;使用DiscardPolicy策略我們可以看到10個(gè)任務(wù)丟棄了5個(gè)。
但是CallerRunsPolicy策略會(huì)讓主線程來(lái)執(zhí)行任務(wù),同時(shí)將主線程阻塞,已達(dá)到延緩提交任務(wù)的效果;當(dāng)主線程執(zhí)行完畢后,線程池內(nèi)任務(wù)也執(zhí)行完畢了,這時(shí)線程池會(huì)繼續(xù)接受后續(xù)任務(wù)。

線程池提交方式

我們可以通過(guò)submit、execute方法提交任務(wù)給線程池執(zhí)行,其中submit方法有三個(gè)重載,他們有以下區(qū)別

方法說(shuō)明是否關(guān)心結(jié)果
void execute無(wú)返回值,提交后任務(wù)和主線程再無(wú)瓜葛
<T> Future<T> submit(Callable<T> task)返回一個(gè)代表執(zhí)行結(jié)果的Future對(duì)象,當(dāng)調(diào)用Future的get方法時(shí)會(huì)獲取到執(zhí)行結(jié)果,如果線程發(fā)生異常會(huì)獲取到異常信息
Future<?> submit(Runnable task)返回一個(gè)代表執(zhí)行結(jié)果的Future對(duì)象,,當(dāng)調(diào)用Future的get方法時(shí),成功返回null,如果線程發(fā)生異常會(huì)獲取到異常信息
<T> Future<T> submit(Runnable task, T result)當(dāng)線程正常結(jié)束的時(shí)候調(diào)用Future的get方法會(huì)返回result對(duì)象,當(dāng)線程拋出異常的時(shí)候會(huì)獲取到對(duì)應(yīng)的異常的信息

線程池優(yōu)化策略

  • CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 NCPU+1

  • IO密集型任務(wù),參考值可以設(shè)置為2*NCPU

Executors

上面ThreadExecutorPool提供給我們手動(dòng)實(shí)現(xiàn)線程池的方式,同時(shí)J.U.C包下面提供了線程池Executors類,可以讓我們方便的創(chuàng)建線程池。
Executors提供了5種線程池的實(shí)現(xiàn)方式,以針對(duì)不同的業(yè)務(wù)場(chǎng)景:
java線程的原理和實(shí)現(xiàn)方式

從上圖中我們可以看到,除了newWorkStealingPool以外,其他的線程池都只是通過(guò)ThreadExecutorPool構(gòu)造函數(shù),傳遞不同的參數(shù)而實(shí)現(xiàn)不同的效果,這也是本篇博客為什么先介紹ThreadExecutorPool的原因;雖然這幾種線程池區(qū)別已經(jīng)一目了然,我們還是列舉一下它們的特點(diǎn):

方法說(shuō)明缺點(diǎn)
newCachedThreadPool初始不指定線程池大小,提交任務(wù)后就創(chuàng)建線程,每隔60s回收一下空閑線程最大并發(fā)數(shù)不可控制:導(dǎo)致系統(tǒng)資源耗盡;不拒絕任務(wù):存在OOM風(fēng)險(xiǎn)
newFixedThreadPool指定固定大小線程池,不存在非核心線程,使用無(wú)界隊(duì)列無(wú)界隊(duì)列:存在OOM風(fēng)險(xiǎn)
newScheduledThreadPool在手動(dòng)創(chuàng)建ThreadExecutorPool的基礎(chǔ)上加了一個(gè)定期任務(wù),例如給線程池提交了兩個(gè)任務(wù),設(shè)置10s運(yùn)行一次這兩個(gè)任務(wù)需要注意ThreadExecutorPool參數(shù)
newSingleThreadExecutor只有一個(gè)線程的線程池,使用無(wú)界隊(duì)列單線程略顯單薄;無(wú)界隊(duì)列:存在OOM風(fēng)險(xiǎn)
newWorkStealingPool返回一個(gè)ForkJoinPool而不是ThreadExecutorPool對(duì)象,可以指定線程數(shù),詳情見下面ForkJoinPool描述適用于大任務(wù)情況
  • ForkJoinPool
    ForkJoinPool適用于大型任務(wù),其核心是Fork和Join;Fork可以將一個(gè)大任務(wù)拆分為多個(gè)小的任務(wù),Join會(huì)將多個(gè)小的任務(wù)的結(jié)果匯總達(dá)到最終運(yùn)行效果。

舉個(gè)例子:假設(shè)一個(gè)線程池中并發(fā)數(shù)控制在兩個(gè),但是每個(gè)任務(wù)都要運(yùn)行1分鐘;假設(shè)我們當(dāng)前服務(wù)器有4個(gè)CPU,兩個(gè)在處理任務(wù),其他的則為空閑狀態(tài),這時(shí)剩下的兩個(gè)空閑CPU資源是浪費(fèi)的。
試想一下:如果們能使剩下的兩個(gè)空閑的CPU也能利用起來(lái)處理上面兩個(gè)任務(wù),任務(wù)運(yùn)行肯定會(huì)加快,這就是ForkJoinPool的設(shè)計(jì)出發(fā)點(diǎn)。

不要使用Executors

多數(shù)情況下,不推薦使用Executors,而推薦手動(dòng)創(chuàng)建ThreadExecutorPool,因?yàn)镋xecutors的五種實(shí)現(xiàn)方式有一下缺點(diǎn):

  • 要么不能控制并發(fā):資源耗盡、OOM

  • 要么不能設(shè)置等待隊(duì)列大小:OOM

  • 不能指定拒絕策略

線程池的相關(guān)方法

方法說(shuō)明
shutdown關(guān)閉線程池,執(zhí)行以前提交的任務(wù),但是不接受新提交的任務(wù)
isTerminated如果調(diào)用了shutdown,并且所有任務(wù)已經(jīng)完成,則返回true,否則永遠(yuǎn)false
getActiveCount線程池存貨的數(shù)量
getQueue().size等待隊(duì)列大小
getPoolSize線程池中當(dāng)前線程數(shù)量
getLargestPoolSize曾經(jīng)有過(guò)的最大線程數(shù)量
getTaskCount未完成的任務(wù)數(shù)量
getCompletedTaskCount完成的任務(wù)數(shù)量

“java線程的原理和實(shí)現(xiàn)方式”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI