1、行程與執行緒
1.1、行程
行程可以看作是程式的執行程序,一個程式的運行需要CPU時間、記憶體空間、檔案以及I/O等資源,作業系統就是以行程為單位來分配這些資源的,所以說行程是分配資源的基本單位,
(1)、行程是動態的,程式是靜態的
程式是靜態的,它本身作為一種軟體資源可以長期保存在磁盤(常說的硬碟)中,比如QQ,QQ作為一個程式,其本身保存在計算機的磁盤上,此時,它并沒有得到CPU、記憶體、I/O等資源,因此當前的QQ程式只是一個靜態的程式并不能給我們實作視頻、語音等功能,
但當QQ程式開始執行時,作業系統就會將QQ程式從磁盤中裝入記憶體,同時也會在作業系統中創建屬于QQ的行程,這些新創建的QQ行程會得到作業系統分配的CPU、記憶體、I/O等資源,得到這些資源后,創建出來的QQ行程就可以實作視頻和語音的功能,而當我們點擊退出QQ后,這些行程就會立刻消亡,分配得到的資源也會被釋放,
由此可以看出,QQ行程是QQ程式的執行程序,它是動態的,有一定的生命周期,會動態的產生和消亡,行程是資源分配的單位,
(2)、程式與行程并不是一 一對應的關系
雖然行程可以看作是程式的執行程序,但并非一個程式對應一個行程,即二者并不是一 一對應的關系,程式與行程的關系可能有以下幾種:
①一個程式產生一個行程:比如Win10的記事本程式(notepad.exe),每打開一個txt文本檔案,就只會啟動一個記事本行程,
②一個程式產生多個行程:比如瀏覽器啟動時,一般都會產生多個行程,這些行程相互配合,互相影響,共同實作瀏覽器的功能,
③一個程式可以被多個行程共用:比如一個記事本程式在執行時,就只會產生一個行程,但當我們再用記事本程式打開一個檔案時,此時就會再次在作業系統中創建一個新的行程,這個新的行程同樣也會呼叫記事本程式,即在此刻,計算機磁盤中只有一個記事本程式,但是作業系統中卻有兩個記事本行程在共用這個程式,且這兩個行程互不影響,
④一個行程又可能要用到多個程式:比如,用C語言寫了一個helloword.c的程式,此時,輸入命令gcc helloword.c,那么作業系統會創建一個行程,它呼叫c編譯程式,對helloword.c檔案進行編譯,這個行程在執行編譯的程序中,除了呼叫c編譯程式和我們撰寫的helloword程式外,還會用到c預處理程式、連接程式、結果輸出程式等,
1.2、執行緒
執行緒從屬于行程,只能在行程的內部活動,多個執行緒共享行程所擁有的的資源,如果把行程看作是完成許多功能的任務的集合,那么執行緒就是集合中的一個任務元素,負責具體的功能,雖然CPU、記憶體、I/O等資源分配給了行程,但實際上真正利用這些資源并在CPU上執行的卻是執行緒,即真正完成程式功能的是執行緒,
因為行程作為這些資源的擁有者,它的負載很重,在行程的創建、切換、洗掉程序中的時間和空間開銷都很大,所以目前主流的作業系統都只將行程作為資源的擁有者,而把CPU調度和運行的屬性賦予了執行緒,
比如打開瀏覽器程式,會產生相應的行程,瀏覽器行程中包含有許多執行緒,如HTTP請求執行緒,I/O執行緒,渲染執行緒,事件回應執行緒等,瀏覽器行程擁有著記憶體和I/O資源等,但當我們在瀏覽器中輸入文字時,真正使用I/O資源接收我們輸入的文字,并在CPU處理文字的卻是瀏覽器行程中的I/O執行緒,即真正完成瀏覽器文字輸入功能的是執行緒,

現代很多作業系統支持讓一個行程包含多個執行緒,從而提高程式的并行程度和資源的利用率,
1.3、執行緒與行程的關系
①一個行程可以有多個執行緒,但至少要有一個執行緒,并且一個執行緒只能在一個行程的地址空間內活動,
②資源分配給行程,而一個行程內的所有執行緒共享該行程的所有資源,
③CPU分配給的是執行緒,即真正在CPU上運行的是執行緒,
④行程間通信較為復雜,同一臺計算機的行程通信稱為 IPC(Inter-process communication),
而不同計算機之間的行程通信,則需要通過網路,并遵守共同的協議,例如 HTTP等,
⑤執行緒通信相對簡單,因為它們共享行程內的記憶體,一個例子是多個執行緒可以訪問同一個共享變數,
⑥執行緒更輕量,執行緒背景關系切換成本一般上要比行程背景關系切換低,
2、Java中的行程與執行緒
2.1、JVM行程
我們知道Java語言是需要運行在JVM上的,實際上,JVM也是一個軟體程式,這就意味著它執行起來也會在作業系統中創建行程,即JVM行程,通常又叫JVM實體,而我們所寫的main方法,實際上就是JVM行程中主執行緒的所在,
從作業系統的角度來看,我們常說的Java程式,應該包括JVM和我們撰寫的Java代碼,
當我們寫完Java代碼,并編譯成class檔案后,使用Java命令執行main方法;或者直接在IDE啟動main方法時,JVM程式就會執行,作業系統會將其從磁盤中裝入記憶體,并創建一個JVM行程,隨后啟動主執行緒,主執行緒會去呼叫某個類的 main 方法,因此這個主執行緒就是我們寫的main方法所在,
實際上,JVM本身就是一個多執行緒應用,即使我們在代碼中并沒有手動的創建執行緒,JVM行程也并不是只有一個主執行緒,而是也會有其他執行緒,這些執行緒完成著JVM的功能,如GC執行緒負責回收JVM使用程序中的垃圾物件,JVM行程啟動完成后,必然會有的執行緒如下:
| 執行緒 | 作用 |
|---|---|
| main | 主執行緒,執行我們指定的啟動類的main方法 |
| Reference Handler | 處理參考的執行緒 |
| Finalizer | 呼叫物件的finalize方法的執行緒 |
| Signal Dispatcher | 分發處理發送給JVM信號的執行緒 |
| Attach Listener | 負責接收外部的命令的執行緒 |
至此,我們知道了,啟動一個Java程式,本質上就是啟動JVM程式,并在作業系統中創建一個JVM行程,這個JVM行程會由作業系統分配許多資源,如記憶體、I/O等,JVM行程中包含有許多執行緒,這些執行緒共享JVM行程分配到的資源,同時這些執行緒也是CPU核心上執行的物體,它們完成著JVM所具有的功能,
那么如果我們啟動兩個Java程式,會生成多少個JVM行程呢?
我們撰寫兩個Java程式,其有代碼如下:
processTest01程式
public class processTest01 {
public static void main(String[] args) throws InterruptedException {
System.out.println("我是測驗01");
byte[] a = new byte[1024*1024*50]; //在堆中占50MB
processTest02 test02 = new processTest02();
Thread.sleep(1000*60*30); //休眠三十分鐘
}
}
processTest02程式
public class processTest02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("我是測驗02");
byte[] a = new byte[1024*1024*900]; //在堆中占900MB
Thread.sleep(1000*60*30); //休眠三十分鐘
}
}
我們將編譯這兩個程式,并分別用Java命令啟動它們,看看兩個Java程式會在作業系統中創建了多少個行程,

打開JDK自帶的jvisualvm.exe,這是JDK提供的查看Java行程和執行緒相關資訊的工具程式,在自己電腦上的JDK目錄下(Win10):Java\jdk1.8.0_131\bin,如圖所示:

可以繼續使用jvisualvm,查看行程中執行緒的相關情況:
pid(行程號)為2146的行程:

pid(行程號)為4196的行程:

可以看出,啟動多少個Java程式,就會創建多少個JVM行程,也稱之為JVM實體,而每一個JVM實體都是獨立的,它們互不影響,這也是前面所說的一個程式可以被多個行程共用的情況,
一個JVM行程就是一個JVM的實體,JVM的實體在執行Java程式的程序中會把它管理的記憶體劃分為不同區域,稱之為運行時資料區,如下所示:

2.2、Java的執行緒
我們知道,執行緒從屬于行程,是CPU調度執行的單位,各個執行緒共享行程內的資源,目前主流的作業系統都支持了執行緒,在實作了執行緒的作業系統中,一個行程中必然有至少一個作業系統的執行緒,這種屬于作業系統的執行緒被稱為內核執行緒(kernel-Level Thread,KLT),
而各個應用程式實作多執行緒的方式主要有三種:
①內核執行緒1:1實作
內核執行緒即作業系統本身的執行緒,1:1意味著程式中的執行緒與作業系統中的內核執行緒是直接對應的,這種執行緒的創建是由作業系統來完成的,同時也是由作業系統來負責調度的,這種內核執行緒的切換需要硬體支持,切換所需的時間也較長,但其優點是一個執行緒阻塞了,其他執行緒也可以執行,則行程就能繼續作業,但一般來說,程式中的執行緒不會直接使用內核執行緒,而是使用它提供的高級介面,稱之為輕量級行程(Light Weight Process,LWP),雖然名稱變了,但其本質上還是作業系統的內核執行緒,每個輕量級行程,都由一個內核執行緒支持,所以他們都可以獨立調度,由作業系統的調度器(Scheduler)負責調度,總的來說就是,程式中的執行緒是作業系統的內核執行緒,

可以看出,使用內核執行緒1:1實作的程式可以同時在多個CPU核心上跑,也就是說,程式執行產生的一個行程中的多個執行緒在同一時刻可能會在不同的CPU核心上運行,這對于一個程式來說,大大加快了運行效率,
②用戶執行緒1:N實作
用戶執行緒指的是由用戶程式自主實作,不需要作業系統來實作的執行緒,一個執行緒不是內核執行緒,就可以認為是用戶執行緒,用戶執行緒雖然不需要作業系統來實作,但在實作了執行緒的作業系統中,一個行程中必然要有一個內核執行緒來支持運行,1:N中的1就是一個內核執行緒的意思,而N指的是用戶程式自主實作的多用戶執行緒,作業系統無法得知這些用戶執行緒的存在,因為這些用戶執行緒都是在用戶程式內部建立、切換和銷毀的,由于用戶執行緒不需要作業系統的幫助,所以對于用戶執行緒的操作可以非常快,消耗低,且不需要硬體的支持,同時,用戶執行緒的的數量不受作業系統的限制,在沒有實作多執行緒的作業系統中也可以實作多執行緒程式,但由于用戶執行緒需要映射為內核執行緒才能執行,所以如果一個執行緒阻塞,那么所有的執行緒都將阻塞,行程也無法繼續作業,執行緒的調度也是由用戶程式自主實作,總的來說,用戶執行緒就是用戶的程式自主實作的執行緒,多個用戶執行緒對應著一個作業系統的內核執行緒,

可以看出,用戶執行緒1:N實作的程式,一個行程中的多用戶執行緒在同一時刻只能在一個CPU核心上運行,因為只有一個內核執行緒支持著這個行程,從作業系統角度來看,這就是一個單執行緒的行程,當然,如果一個實作了用戶執行緒的程式執行產生了多個行程,那么實際上這個程式也可能在多個CPU核心上跑,目前很少有程式實作這種用戶執行緒了,
③混合N:M實作
混合實作即用戶執行緒和內核執行緒一起使用的實作方式,在這種混合實作下,即存在用戶執行緒,又存在輕量級行程(內核執行緒),用戶執行緒還是由用戶程式自主實作,這樣用戶執行緒的創建、切換、銷毀依然快速且消耗低,而一個用戶執行緒的集合(包含一個或多個用戶執行緒)又與一個內核執行緒映射,多個用戶執行緒的集合,就是N:M實作中的N,而M自然指的是多個內核執行緒,這樣的情況下,也可以繼續使用作業系統的調度功能,而且由于一個內核執行緒支持著一個用戶執行緒的集合,所以一個用戶執行緒阻塞,并不會阻塞其他用戶執行緒,行程也能繼續工佐,總的來說,混合實作就是一個用戶執行緒的集合對應著一個內核執行緒,一個行程中會存在多個用戶執行緒集合,則會有多個內核執行緒來支持運行,

可以看出,由于用戶執行緒集合映射到了一個內核執行緒上,而一個行程又有多個用戶執行緒集合,所以使用混合實作多執行緒的程式,行程中也可能存在多個用戶執行緒在不同CPU核心上執行的情況,
Java執行緒的實作
介紹完程式實作多執行緒的三種方式,那么Java是如何實作多執行緒的呢?
首先,Java虛擬機規范并未規定要如何實作多執行緒,所以Java的執行緒都是由虛擬機來具體實作,不同的虛擬機實作執行緒的方式可能都不相同,不過,在JDK1.2之前,早期的虛擬機都采用的是用戶執行緒1:N的實作方式,而在JDK1.3之后,大部分的虛擬機都采用了內核執行緒1:1實作的方式,包括我們最常用的HostSpot虛擬機,
這就意味著,我們在平常的開發中,不論是JVM程式自己創建的執行緒,還是我們手動編碼創建的執行緒,實際上都是直接1:1映射到了作業系統的內核執行緒, 這一內核執行緒由作業系統來創建,且虛擬機不會去干涉執行緒調度,Java的執行緒何時交給CPU核心去執行,交給哪個CPU核心,執行緒有多少CPU核心的執行時間,執行緒何時凍結、喚醒等等,都交給作業系統去完成,也都是作業系統全權決定(不過Java虛擬機也可以設定執行緒優先級來給作業系統的執行緒調度提供建議),
3、多執行緒與并行、并發
兩個或兩個以上的執行緒在同一時刻發生就稱為并行,如兩個執行緒在同一時刻在兩個不同的CPU核心上執行,則可以說這兩個執行緒是并行執行,
兩個或兩個以上的執行緒在同一時間段內發生則稱為并發,比如兩個執行緒在一個極短的時間段上分別在同一個CPU核心上執行,則可以說這兩個執行緒是并發執行,
并行與并發的關鍵就在于是否為同一時刻執行,并行是在同一時刻執行,而并發則是在極短的時間內執行,
在一個CPU核心中,執行緒實際是并發執行的,作業系統中有一個組件叫做任務調度器,將cpu核心的時間片(windows下時間片最小約為 15 毫秒)分給不同的程式使用,只是由于cpu核心在執行緒間(時間片很短)的切換非常快,給人的感覺是同時運行的,但實際上一個CPU核心同一時刻只能支持一個執行緒運行,即如果是在單個CPU核心的作業系統中,Java程式(包含JVM)本身雖然是多執行緒的,但實際上,同一時刻只能有一個Java執行緒在執行,

但目前的計算機已經很少有單個核心的CPU了,目前即使是個人使用的計算機都是多個核心的CPU了,每個核心都可以獨立調度運行執行緒,這就意味著執行緒之間可以并行執行,

可以看出,在多核心的CPU下,執行緒之間是可以并行執行的,但即使是擁有多個CPU核心的計算機,CPU核心的數量始終是有限的,而一個作業系統中的執行緒數遠遠多于CPU核心數,所以執行緒之間大部分情況下是屬于并發狀態的,即執行緒之間是在極短時間下交替在CPU核心上執行的,
需要注意的是,在單個CPU核心下,多執行緒其實是沒有太大意義的,因為始終只能有一個執行緒在CPU核心上執行,而執行緒間的切換是需要耗費時間和資源的,但多核CPU可以同時執行執行緒,如果在多核CPU中還是使用單執行緒,無疑是對CPU的巨大浪費,并發的最主要的目的就是最大限度利用CPU資源,
但并發并不是執行緒特有的,行程之間也可以并發,有些語言實作并發就是使用行程來進行并發,如PHP,不過Java的并發依然是依賴于多執行緒,即多執行緒是Java實作并發的一種方式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231289.html
標籤:Java
