溫馨提示×

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

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

系統(tǒng)線程的實(shí)現(xiàn)原理是什么

發(fā)布時(shí)間:2021-10-25 14:53:15 來(lái)源:億速云 閱讀:165 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“系統(tǒng)線程的實(shí)現(xiàn)原理是什么”,在日常操作中,相信很多人在系統(tǒng)線程的實(shí)現(xiàn)原理是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”系統(tǒng)線程的實(shí)現(xiàn)原理是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

各種操作系統(tǒng)均提供了線程的實(shí)現(xiàn)(內(nèi)核線程),線程是 CPU 進(jìn)行工作調(diào)度的基本單位
線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位,線程的引入,可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開,各個(gè)線程既可以共享進(jìn)程資源(內(nèi)存地址、文件I/O等),又可以獨(dú)立調(diào)度(線程是CPU調(diào)度的基本單位)。而編程語(yǔ)言一般都會(huì)提供操作內(nèi)核線程的 API, Java 也不例外。
操作內(nèi)核線程的模型主要有如下三種:
  1. 使用內(nèi)核線程(1:1 模型)

  2. 使用用戶線程(1:N 模型)

  3. 使用用戶線程 + 輕量級(jí)進(jìn)程(LWP)(N:M 模型)

基礎(chǔ)概念復(fù)習(xí)     

我們先復(fù)習(xí)下操作系統(tǒng)中的幾個(gè)關(guān)鍵概念:

  • 內(nèi)核線程 KLT:內(nèi)核級(jí)線程(Kemel-Level Threads, KLT 也有叫做內(nèi)核支持的線程),直接由操作系統(tǒng)內(nèi)核支持,線程創(chuàng)建、銷毀、切換開銷較大
  • 用戶線程 UT:用戶線程(User Thread,UT),建立在用戶空間,系統(tǒng)內(nèi)核不能感知用戶線程的存在,線程創(chuàng)建、銷毀、切換開銷小
  • 輕量級(jí)進(jìn)程 LWP:           (LWP,Light weight process)用戶級(jí)線程和內(nèi)核級(jí)線程之間的中間層,是由操作系統(tǒng)提供給用戶的操作內(nèi)核線程的接口的實(shí)現(xiàn) 。
  • 進(jìn)程 P:用戶進(jìn)程 

操作系統(tǒng)的三種線程模型

下面依次介紹三種線程模型:

內(nèi)核線程模型:

內(nèi)核線程模型即完全依賴操作系統(tǒng)內(nèi)核提供的內(nèi)核線程(Kernel-Level Thread ,KLT)來(lái)實(shí)現(xiàn)多線程。在此模型下,線程的切換調(diào)度由系統(tǒng)內(nèi)核完成,系統(tǒng)內(nèi)核負(fù)責(zé)將多個(gè)線程執(zhí)行的任務(wù)映射到各個(gè)CPU中去執(zhí)行。

程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process,LWP),輕量級(jí)進(jìn)程就是我們通常意義上所講的線程,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持,因此只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程。這種輕量級(jí)進(jìn)程與內(nèi)核線程之間1:1的關(guān)系稱為一對(duì)一的線程模型。         

系統(tǒng)線程的實(shí)現(xiàn)原理是什么


  • 用戶線程模型:

    從廣義上來(lái)講,一個(gè)線程只要不是內(nèi)核線程,就可以認(rèn)為是用戶線程(User Thread,UT),因此,從這個(gè)定義上來(lái)講,輕量級(jí)進(jìn)程也屬于用戶線程,但輕量級(jí)進(jìn)程的實(shí)現(xiàn)始終是建立在內(nèi)核之上的,許多操作都要進(jìn)行系統(tǒng)調(diào)用,效率會(huì)受到限制。

    使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核支援,劣勢(shì)也在于沒(méi)有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶程序自己處理。線程的創(chuàng)建、切換和調(diào)度都是需要考慮的問(wèn)題,而且由于操作系統(tǒng)只把處理器資源分配到進(jìn)程,那諸如“阻塞如何處理”、“多處理器系統(tǒng)中如何將線程映射到其他處理器上”這類問(wèn)題解決起來(lái)將會(huì)異常困難,甚至不可能完成。

    因而使用用戶線程實(shí)現(xiàn)的程序一般都比較復(fù)雜,此處所講的“復(fù)雜”與“程序自己完成線程操作”,并不限制程序中必須編寫了復(fù)雜的實(shí)現(xiàn)用戶線程的代碼,使用用戶線程的程序,很多都依賴特定的線程庫(kù)來(lái)完成基本的線程操作,這些復(fù)雜性都封裝在線程庫(kù)之中,除了以前在不支持多線程的操作系統(tǒng)中(如DOS)的多線程程序與少數(shù)有特殊需求的程序外,現(xiàn)在使用用戶線程的程序越來(lái)越少了,Java、Ruby等語(yǔ)言都曾經(jīng)使用過(guò)用戶線程,最終又都放棄使用它。


             

系統(tǒng)線程的實(shí)現(xiàn)原理是什么


混合線程模型:

線程除了依賴內(nèi)核線程實(shí)現(xiàn)和完全由用戶程序自己實(shí)現(xiàn)之外,還有一種將內(nèi)核線程與用戶線程一起使用的實(shí)現(xiàn)方式。在這種混合實(shí)現(xiàn)下,既存在用戶線程,也存在輕量級(jí)進(jìn)程。

用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建、切換、析構(gòu)等操作依然廉價(jià),并且可以支持大規(guī)模的用戶線程并發(fā)。而操作系統(tǒng)提供支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過(guò)輕量級(jí)線程來(lái)完成,大大降低了整個(gè)進(jìn)程被完全阻塞的風(fēng)險(xiǎn)。

在這種混合模式中,用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,即為N:M的關(guān)系。許多UNIX系列的操作系統(tǒng),如Solaris、HP-UX等都提供了N:M的線程模型實(shí)現(xiàn)。

對(duì)于Sun JDK來(lái)說(shuō),它的Windows版與Linux版都是使用一對(duì)一的線程模型實(shí)現(xiàn)的,一條Java線程就映射到一條輕量級(jí)進(jìn)程之中,因?yàn)閃indows和Linux系統(tǒng)提供的線程模型就是一對(duì)一的。在Solaris平臺(tái)中,由于操作系統(tǒng)的線程特性可以同時(shí)支持一對(duì)一(通過(guò)Bound Threads或Alternate Libthread實(shí)現(xiàn))及多對(duì)多(通過(guò)LWP/Thread Based Synchronization實(shí)現(xiàn))的線程模型,因此在Solaris版的JDK中也對(duì)應(yīng)提供了兩個(gè)平臺(tái)專有的虛擬機(jī)參數(shù):-XX:+UseLWPSynchronization(默認(rèn)值)和-XX:+UseBoundThreads來(lái)明確指定虛擬機(jī)使用哪種線程模型。

         

系統(tǒng)線程的實(shí)現(xiàn)原理是什么操作系統(tǒng)的線程調(diào)度方式           

線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程。

主要的線程調(diào)度方式有兩種,分別是 協(xié)同式線程調(diào)度(Cooperative Threads-Scheduling)和 搶占式線程調(diào)度(Preemptive Threads-Scheduling)

協(xié)同式調(diào)度
如果使用協(xié)同式調(diào)度的多線程系統(tǒng),線程的執(zhí)行時(shí)間由線程本身來(lái)控制,線程把自己的工作執(zhí)行完了之后,要主動(dòng)通知系統(tǒng)切換到另外一個(gè)線程上。
協(xié)同式多線程的最大好處是實(shí)現(xiàn)簡(jiǎn)單,而且由于線程要把自己的事情干完后才會(huì)進(jìn)行線程切換,切換操作對(duì)線程自己是可知的,所以沒(méi)有什么線程同步的問(wèn)題。
Lua語(yǔ)言中的“協(xié)同例程”就是這類實(shí)現(xiàn)。它的壞處也很明顯:線程執(zhí)行時(shí)間不可控制,甚至如果一個(gè)線程編寫有問(wèn)題,一直不告知系統(tǒng)進(jìn)行線程切換,那么程序就會(huì)一直阻塞在那里。
很久以前的Windows 3.x系統(tǒng)就是使用協(xié)同式來(lái)實(shí)現(xiàn)多進(jìn)程多任務(wù)的,相當(dāng)不穩(wěn)定,一個(gè)進(jìn)程堅(jiān)持不讓出CPU執(zhí)行時(shí)間就可能會(huì)導(dǎo)致整個(gè)系統(tǒng)崩潰。

搶占式調(diào)度

如果使用搶占式調(diào)度的多線程系統(tǒng),那么每個(gè)線程將由系統(tǒng)來(lái)分配執(zhí)行時(shí)間,線程的切換不由線程本身來(lái)決定(在Java中,Thread.yield()可以讓出執(zhí)行時(shí)間,但是要獲取執(zhí)行時(shí)間的話,線程本身是沒(méi)有什么辦法的)。

在這種實(shí)現(xiàn)線程調(diào)度的方式下,線程的執(zhí)行時(shí)間是系統(tǒng)可控的,也不會(huì)有一個(gè)線程導(dǎo)致整個(gè)進(jìn)程阻塞的問(wèn)題。

Java使用的線程調(diào)度方式就是搶占式調(diào)度。在JDK后續(xù)版本中有可能會(huì)提供協(xié)程(Coroutines)方式來(lái)進(jìn)行多任務(wù)處理。

與前面所說(shuō)的Windows 3.x的例子相對(duì),在Windows 9x/NT內(nèi)核中就是使用搶占式來(lái)實(shí)現(xiàn)多進(jìn)程的,當(dāng)一個(gè)進(jìn)程出了問(wèn)題,我們還可以使用任務(wù)管理器把這個(gè)進(jìn)程“殺掉”,而不至于導(dǎo)致系統(tǒng)崩潰。

線程優(yōu)先級(jí)

雖然Java線程調(diào)度是系統(tǒng)自動(dòng)完成的,但是我們還是可以“建議”系統(tǒng)給某些線程多分配一點(diǎn)執(zhí)行時(shí)間,另外的一些線程則可以少分配一點(diǎn)——這項(xiàng)操作可以通過(guò)設(shè)置線程優(yōu)先級(jí)來(lái)完成。

Java語(yǔ)言一共設(shè)置了10個(gè)級(jí)別的線程優(yōu)先級(jí)(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在兩個(gè)線程同時(shí)處于Ready狀態(tài)時(shí),優(yōu)先級(jí)越高的線程越容易被系統(tǒng)選擇執(zhí)行。

不過(guò),線程優(yōu)先級(jí)并不是太靠譜,原因是Java的線程是通過(guò)映射到系統(tǒng)的原生線程上來(lái)實(shí)現(xiàn)的,所以線程調(diào)度最終還是取決于操作系統(tǒng),雖然現(xiàn)在很多操作系統(tǒng)都提供線程優(yōu)先級(jí)的概念,但是并不見得能與Java線程的優(yōu)先級(jí)一一對(duì)應(yīng)。

如Solaris中有2147483648(232)種優(yōu)先級(jí),但Windows中就只有7種,比Java線程優(yōu)先級(jí)多的系統(tǒng)還好說(shuō),中間留下一點(diǎn)空位就可以了,但比Java線程優(yōu)先級(jí)少的系統(tǒng),就不得不出現(xiàn)幾個(gè)優(yōu)先級(jí)相同的情況了。


系統(tǒng)線程的實(shí)現(xiàn)原理是什么

上圖顯示了Java線程優(yōu)先級(jí)與Windows線程優(yōu)先級(jí)之間的對(duì)應(yīng)關(guān)系,Windows平臺(tái)的JDK中使用了除THREAD_PRIORITY_IDLE之外的其余6種線程優(yōu)先級(jí)。
Java 線程狀態(tài)

Java語(yǔ)言定義了5種線程狀態(tài),在任意一個(gè)時(shí)間點(diǎn),一個(gè)線程只能有且只有其中的一種狀態(tài),這5種狀態(tài)分別如下:


系統(tǒng)線程的實(shí)現(xiàn)原理是什么


  1. 新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)。


  2. 運(yùn)行(Runable):Runable包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU為它分配執(zhí)行時(shí)間。


  3. 無(wú)限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待被其他線程顯式地喚醒。


    以下方法會(huì)讓線程陷入無(wú)限期的等待狀態(tài):沒(méi)有設(shè)置Timeout參數(shù)的Object.wait()方法。沒(méi)有設(shè)置Timeout參數(shù)的Thread.join()方法。LockSupport.park()方法。


  4. 限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過(guò)無(wú)須等待被其他線程顯式地喚醒,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒。

    以下方法會(huì)讓線程進(jìn)入限期等待狀態(tài):Thread.sleep()方法。設(shè)置了Timeout參數(shù)的Object.wait()方法。設(shè)置了Timeout參數(shù)的Thread.join()方法。LockSupport.parkNanos()方法。LockSupport.parkUntil()方法。


  5. 阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取到一個(gè)排他鎖,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;而“等待狀態(tài)”則是在等待一段時(shí)間,或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)。


  6. 結(jié)束(Terminated):已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行。

Java 多線程實(shí)現(xiàn)          

實(shí)現(xiàn)1:繼承Thread類

// 繼承 Threadpublic class MyThread extends Thread {    @Override    public void run() {        System.out.println("MyThread run...");    }}

實(shí)現(xiàn)2:實(shí)現(xiàn) Runnable 接口

public class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println("MyRunnable run...");    }}
       

實(shí)現(xiàn)3:實(shí)現(xiàn) Callable 接口,使用 FutureTask 獲取異步返回值

  public static void main(String[] args) throws ExecutionException, InterruptedException {    class MyCallable implements Callable<String> {      @Override      public String call() throws Exception {        return "MyCallable";      }    }    FutureTask<String> task = new FutureTask<>(new MyCallable());    Thread c = new Thread(task);    c.start();    System.out.println(task.get());  }


實(shí)現(xiàn)4:JDK8以上版本使用 CompletableFuture 進(jìn)行異步計(jì)算。

在Java8中,提供了非常強(qiáng)大的Future的擴(kuò)展功能,可以幫助我們簡(jiǎn)化異步編程的復(fù)雜性,并且提供了函數(shù)式編程的能力,可以通過(guò)回調(diào)的方式處理計(jì)算結(jié)果,也提供了轉(zhuǎn)換和組合 CompletableFuture 的方法。

public class CompletableFutureTest {  public static void main(String[] args) {    ExecutorService threadPool = Executors.newFixedThreadPool(2);    // JDK1.8 提供的 CompletableFuture    CompletableFuture<String> futureTask = CompletableFuture.supplyAsync(new Supplier<String>() {      @Override      public String get() {        System.out.println("task start");        try {          Thread.sleep(10000);        } catch (Exception e) {          e.printStackTrace();          return "execute failure";        }        System.out.println("task end");        return "execute success";      }    }, threadPool);    // 異步獲取 futureTask 的執(zhí)行結(jié)果,此處代碼可以跟其他流程代碼放在一起    futureTask.thenAccept(e-> System.out.println("future task result:" + e));    System.out.println("main thread end");  }}
輸出結(jié)果:task startmain thread endtask endfuture task result:execute success


實(shí)現(xiàn)5:使用線程池,ThreadPoolExecutor 類。

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue<Runnable> workQueue,                          ThreadFactory threadFactory) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);}<T> Future<T> submit(Callable<T> task);Future<?> submit(Runnable task);

到此,關(guān)于“系統(tǒng)線程的實(shí)現(xiàn)原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(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