進程是作業系統進行資源分配的最小單位,其中包括:CPU、記憶體空間、磁碟IO 等、同一進程中的多條執行緒共享該進程中的全部系統資源,而進程和進程直接是相互獨立的。進程是具有一定獨立功能的程式關於某個資料集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。
進程是程式在電腦上的一次執行活動。當你執行一個程序,你就啟動了一個進程。顯然程式是死的、靜態的、進程是活動的、動態的。進程可以分為系統進程和使用者進程。凡是用於完成作業系統的各種功能的進程就是系統進程,它們是處於運行狀態下的作業系統本身,使用者進程就是所有由你啟動的進程。
#執行緒是進程的一個實體,是CPU調度和分派的基本單位,它是比經常更小的、能夠獨立運作的基本單位。線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組寄存器和堆疊),但是它可以與同一個進程的其他線程共享進程所擁有的全部資源。
任何一個程式都必須建立線程,特別是Java不管任何程式都必須啟動一個main函數的主執行緒;Java web開發的定時任務、定時器、JSP和Servlet、非同步訊息處理機制,遠端存取介面RM 等,任何一個監聽事件,onClick的觸發事件等都離不開線程和並發的知識。
多核心:也指單晶片多處理器(Chip Multiprocessors,簡稱CMP),CMP是由美國史丹佛大學提出的,其思想是將大規模並行處理器中的SMP(對稱處理器)整合到同一晶片內,各個處理器並行執行不同的進程。這種依靠多個CPU同時並行的運行程序是實現超高速運算的一個重要方向,稱為平行處理。
多執行緒:Simultaneous Multithreading.簡稱SMT.讓同一個處理器上的多個執行緒同步執行並共享處理器的執行資源。
核心數、執行緒數:目前主流CPU都是多核心的。增加核心數目的就是為了增加執行緒數,因為作業系統是透過執行緒來執行任務的,一般情況下它們是1:1對應關係,也就是說四核心CPU#一般擁有四個線程。但Intel引入超執行緒技術後,使核心數與執行緒數形成了1:2的關係。
我們平常在開發的時候,感覺並沒有受CPU 核心數的限制,想啟動執行緒就啟動線程,哪怕是在單核心CPU 上,為什麼?這是因為作業系統提供了一個CPU 時間片輪轉機制。
時間片輪換調度是一種最古老、最簡單、最公平且使用最廣的演算法,又稱為RR 調度。每個行程被分配一個時間段,稱為它的時間片,也就是該行程運行運行的時間。
我們舉個例子,如果有高速公路A,上面有4條車道,那麼最大並行車輛就是4輛,這條高速公路同時並排行走的車輛小與等於4的時候,車輛就可以並行行駛。 CPU也是這個原理,一個CPU相當於一條高速公路,核心數或執行緒數就相當於並排可以通行的車輛;而多個CPU就相當於有多條高速公路,而每個高速公路並排多個車道。
當談論並發的時候,一定要加個單位時間,也就是說單位時間內並發量是多少?離開單位時間其實是沒有意義的。
俗話說一心不能二用,這對電腦也一樣,原則上一個CPU#只能分配給一個進程,以便運行這個進程。我們通常使用的電腦只有一個CPU,也就是說只有一顆心,要讓它一心多用同時運行多個進程,就必須使用並發技術。實現並發技術相當複雜,最容易理解的是「時間片輪轉進程調度演算法」。
並發:
指應用程式能夠交替執行不同的任務,例如單一CPU核心下執行多執行緒並非同時執行多個任何,如果你開兩個執行緒執行,就是在你幾乎不可察覺的速度不斷去切換執行這兩個任務,以達到「同時執行」效果,只是電腦的執行速度太快,我們無法察覺到而已。
並行:指應用程式能夠同時執行不同的任務,例如:吃飯的時候可以邊吃邊看電視,這兩件事可以同時執行。
**並發和並行兩者的差異就是:一個是交替執行,一個是同時執行**
由於多核心CPU的誕生,多執行緒、高並發的程式設計越來越受重視和關注。
從上面CPU的介紹,可以看出現在市面上沒有CPU的核心不使用多線程並發機制的,特別是伺服器還不只一個CPU。程式的基本調度單元是線程,一個線程也只能在一個一個CPU 的一個核的一個線程跑,如果你是個i3的CPU的話,最差也是雙核心4線程的運算能力:如果是線程的話,那就會浪費釣3/4的CPU效能:如果設計多執行緒的話,那它就可以同時在多個CPU 的多個核的多個執行緒上跑,可以充分的利用CPU,減少CPU的空閒時間,發揮它的運算能力,提高並發量。
例如我們經常使用的下載功能,很多朋友都會開通某一個會員,因為會員版本啟用了多個線程去下載,誰都無法忍受一個線程去下,為什麼呢?因為多執行緒下載快啊。
我們做程式開發的時候,網頁速度提升1s,如果用戶量大的話,就能增加不少轉換量。我們常瀏覽的網頁中,瀏覽器在載入頁面的時候,都會去多開幾個執行緒去載入網路資源,提升網站的相應速度。 多執行緒和高並發,在電腦中,無所不在。
例如我們做一個電商項目,下訂單和給用戶發送簡訊、郵件就可以進行拆分,將發送簡訊給用戶、郵件這兩個步驟獨立成兩個單獨的模組,交給其他執行緒去執行。這樣即增加了非同步的操作,提示了系統效能,又使程式模組化,清晰化和簡化。
在同一個行程裡面的多執行緒是資源共享的,也就是都可以訪問同一個記憶體位址當中的一個變數。
例如:若每個執行緒中對全域變數、靜態變數只讀操作,而無寫操作,一般來說,這個全域變數是執行緒安全的;若有多個執行緒同時執行寫入操作,一般都需要考慮線程同步,否則就可能影響線程安全。
為了解決執行緒之間的安全性引入了Java 鎖的機制,而一不小心就會產生Java 執行緒死鎖的多執行緒問題,因為不同的執行緒都在等待哪些根本不可能被釋放的鎖,導致所有的工作都無法完成。
假設有兩個飢餓的人,他們必須共享刀叉並輪流吃飯,他們都需要獲得兩個鎖,共享刀和共享叉。假如線程A獲得了刀,而線程B獲得了叉。線程A就會進入阻塞狀態來等待獲得叉,而線程B則主帥來等待線程A所擁有的刀。這只是人為設計的例子,單儘管在運行時很難探測到,這類情況卻時常發生。
線程數太多有可能造成系統創建大量線程,而導致消耗完系統記憶體以及CPU的“過渡切換”,造成系統的死機,那麼我們改如果解決這類問題呢?
某些系統資源是有限的,如檔案描述。多執行緒程式可能會耗盡資源,因為每個執行緒都可能希望有一個這樣的資源。如果執行緒數相當大,或某個資源的侯選線 程數遠遠超過了可用的資源數則最好使用資源池。一個最好的範例是資料庫連接池。只要線程需要使用一個資料庫連接,它就從池中取出一個,使用以後再將它返回池中。 資源池也稱為資源庫。
多執行緒應用開發的注意事項很多,希望大家在日後的工作中可以慢慢體會它 的危險所在。
執行緒之間相互配合,完成某項工作,例如:一個執行緒修改了一個物件的值, 而另一個執行緒感知到了變化,然後進行對應的操作,整個過程開始於一個線程, 而最終執行又是另一個線程。前者是生產者,後者就是消費者,這種模式隔離了「做什麼」(what)和「怎麼做」(How),簡單的辦法是讓消費者線程不斷地循環檢查變數是否符合預期在while循環中設定不滿足的條件,如果條件滿足則退出while 循環,從而完成消費者的工作。
卻有以下問題:
難以確保及時性。
難以降低開銷。如果降低睡眠的時間,例如休眠 1 毫秒,這樣消費者能 更迅速地發現條件變化,但是卻可能消耗更多的處理器資源,造成了無端的浪費。
等待/通知機制:是指一個執行緒A 呼叫了物件O 的wait()方法進入等待狀態,而另一個執行緒B 呼叫了物件O的notify()或notifyAll()方法,在執行緒A 收到通知後從物件O 的wait() 方法返回,進而執行後續操作。上述兩個執行緒透過物件 O 來完成交互,而物件 上的 wait()和 notify/notifyAll()的關係就如同開關訊號一樣,用來完成等待方和通 知方之間的交互工作。
notify():通知一個在物件上等待的線程,使其從wait 方法返回,而返回的前提是該線程獲取到了對象的鎖,沒有獲得鎖的線程重新進入WAITING 狀態。
notifyAll():通知所有等待在該物件上的執行緒
wait():呼叫該方法的執行緒進入WAITING 狀態,只有等待另外執行緒的通知或被中斷才會回傳.需要注意,呼叫wait()方法後,會釋放物件的鎖。
wait(long):超時等待一段時間,這裡的參數時間是毫秒,也就是等待長達n 毫秒,如果沒有 通知就超時返回。
wait (long,int):對於超時時間更細粒度的控制,可以達到奈秒
等待和通知的標準範式等待方遵循如下原則。
取得物件的鎖定。
如果條件不滿足,那麼呼叫物件的 wait()方法,被通知後仍要檢查條件。
條件滿足則執行對應的邏輯。
#通知方遵循以下原則:
notify 和notifyAll 應該用誰
盡可能用notifyAll(),謹慎使用notify(),因為notify()只會喚醒一個線程,我們無法確保被喚醒的這個執行緒一定就是我們需要喚醒的執行緒。以上是Java中的多執行緒共享變數與協作機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!