Java多執行緒(一)多執行緒入門篇

_雲起_發表於2018-07-23

1 說到執行緒,首先來說下程式,以下是程式的定義:

程式是作業系統結構的基礎,是程式的一次執行,是一個程式及其資料結構在處理機上順序執行時所發生的活動,是程式在一個資料集合上執行的過程,它是系統進行資源分配和排程的一個獨立單位。

簡單來說一個工作管理員中列表的一個exe檔案就可以理解成程式,如QQ.exe就是一個程式,程式是受系統管理的基本執行單元。

1.1 什麼是執行緒?

執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程式之中,是程式中的實際運作單位。

簡單來說,執行緒可以理解成為在程式中獨立執行的子任務。比如,QQ.exe執行中就有很多的子任務在同時執行。

1.2 程式和執行緒的區別

1.2.1 排程:執行緒作為排程和分配的基本單位,程式作為擁有資源的基本單位 。

1.2.2 併發性:不僅程式之間可以併發執行,同一個程式的多個執行緒之間也可併發執行。

1.2.3 擁有資源:程式是擁有資源的一個獨立單位,執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器、一組暫存器和棧),但是它可以與同屬一個程式的其他執行緒共享程式所擁有的全部資源。程式之間是不能共享地址空間的, 而執行緒是共享著所在程式的地址空間的。

1.2.4 系統開銷:在建立或撤消程式時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消執行緒時的開銷。

1.3 什麼是多執行緒?

多執行緒就是幾乎同時執行多個執行緒。

1.4 為什麼要使用多執行緒

1.4.1 使用執行緒可以把佔據時間長的程式中的任務放到後臺去處理。

1.4.2 使用者介面更加吸引人,這樣比如使用者點選了一個按鈕去觸發某件事件的處理,可以彈出一個進度條來顯示處理的進度。

1.4.3 程式的執行效率可能會提高。

1.4.4 在一些等待的任務實現上如使用者輸入,檔案讀取和網路收發資料等,執行緒就比較有用了。

2 執行緒的狀態

一般來說,執行緒包括以下這幾個狀態:建立(new)、就緒(runnable)、執行(running)、阻塞(blocked)、timed_waiting、waiting、消亡(dead)。


Java多執行緒(一)多執行緒入門篇

3 多執行緒的使用方式

3.1 繼承 Thread類


Java多執行緒(一)多執行緒入門篇

可以看到程式在交替執行,但是最終都會執行到98。

3.2 實現Runnable介面



Java多執行緒(一)多執行緒入門篇

依舊可以看到程式在交替執行,但是最終都會執行到98。

3.3 繼承實現Callable介面


Java多執行緒(一)多執行緒入門篇

這裡需要說明的是實現Callable介面,必須重寫call()方法,並且要用到FutureTask類,這裡先不做介紹,等後面更新執行緒池再細講。

3.4 使用執行緒池例如用Executor框架

這部分暫不介紹。後面更新執行緒池和Executor會詳細介紹。

4 使用多執行緒一定快嗎?

答:不一定,因為多執行緒會進行上下文切換,上下文切換會帶來開銷。

4.1 什麼是上下文切換?

對於單核 CPU,CPU 在一個時刻只能執行一個執行緒,當在執行一個執行緒的過程中轉去執行另外一個執行緒,這個叫做執行緒上下文切換(對於程式也是類似)。執行緒上下文切換過程中會記錄 程式計數器、CPU 暫存器 的 狀態等資料。

4.2 如何減少上下文切換?

4.2.1 減少執行緒的數量

由於一個CPU每個時刻只能執行一條執行緒,而傲嬌的我們又想讓程式併發執行,作業系統只好不斷地進行上下文切換來使我們從感官上覺得程式是併發執的行。因此,我們只要減少執行緒的數量,就能減少上下文切換的次數。

然而如果執行緒數量已經少於CPU核數,每個CPU執行一條執行緒,照理來說CPU不需要進行上下文切換了,但事實並非如此。

4.2.2 控制同一把鎖上的執行緒數量

如果多條執行緒共用同一把鎖,那麼當一條執行緒獲得鎖後,其他執行緒就會被阻塞;當該執行緒釋放鎖後,作業系統會從被阻塞的執行緒中選一條執行,從而又會出現上下文切換。

因此,減少同一把鎖上的執行緒數量也能減少上下文切換的次數。

4.2.3 採用無鎖併發程式設計

需要併發執行的任務是無狀態的:HASH分段

所謂無狀態是指併發執行的任務沒有共享變數,他們都獨立執行。對於這種型別的任務可以按照ID進行HASH分段,每段用一條執行緒去執行。

需要併發執行的任務是有狀態的:CAS演算法

如果任務需要修改共享變數,那麼必須要控制執行緒的執行順序,否則會出現安全性問題。你可以給任務加鎖,保證任務的原子性與可見性,但這會引起阻塞,從而發生上下文切換;為了避免上下文切換,你可以使用CAS演算法, 僅線上程內部需要更新共享變數時使用CAS演算法來更新,這種方式不會阻塞執行緒,並保證更新過程的安全性。


5 使用多執行緒的缺點:

5.1 上下文切換的開銷

當 CPU 從執行一個執行緒切換到執行另外一個執行緒的時候,它需要先儲存當前執行緒的本地的資料,程式指標等,然後載入另一個執行緒的本地資料,程式指標等,最後才開始執行。這種切換稱為“上下文切換”。CPU 會在一個上下文中執行一個執行緒,然後切換到另外一個上下文中執行另外一個執行緒。上下文切換並不廉價。如果沒有必要,應該減少上下文切換的發生。

5.2 增加資源消耗

執行緒在執行的時候需要從計算機裡面得到一些資源。 除了 CPU,執行緒還需要一些 記憶體來維持它本地的堆疊。它也需要 佔用作業系統中一些資源來管理執行緒。

5.3 程式設計更復雜

在多執行緒訪問共享資料的時候,要考慮 執行緒安全問題 。

6 執行緒安全

6.1 執行緒安全定義

一個類在可以被多個執行緒安全呼叫時就是執行緒安全的。

6.2 執行緒安全分類

執行緒安全不是一個非真即假的命題,可以將共享資料按照安全程度的強弱順序分成以下五類:

不可變、絕對執行緒安全、相對執行緒安全、執行緒相容和執行緒對立。

6.2.1. 不可變

不可變(Immutable)的物件一定是執行緒安全的,無論是物件的方法實現還是方法的呼叫者,都不需要再採取任何的執行緒安全保障措施,只要一個不可變的物件被正確地構建出來,那其外部的可見狀態永遠也不會改變,永遠也不會看到它在多個執行緒之中處於不一致的狀態。

不可變的型別:final 關鍵字修飾的基本資料型別;String ;列舉型別Number 部分子類,如 Long 和 Double 等數值包裝型別,BigInteger 和 BigDecimal 等大資料型別。但同為 Number 的子型別的原子類 AtomicInteger 和 AtomicLong 則並非不可變的。

6.2.2 絕對執行緒安全

不管執行時環境如何,呼叫者都不需要任何額外的同步措施。

6.2.3 相對執行緒安全

相對的執行緒安全需要保證對這個物件單獨的操作是執行緒安全的,在呼叫的時候不需要做額外的保障措施,但是對於一些特定順序的連續呼叫,就可能需要在呼叫端使用額外的同步手段來保證呼叫的正確性。

6.2.4 執行緒相容

執行緒相容是指物件本身並不是執行緒安全的,但是可以通過在呼叫端正確地使用同步手段來保證物件在併發環境中可以安全地使用,我們平常說一個類不是執行緒安全的,絕大多數時候指的是這一種情況。Java API 中大部分的類都是屬於執行緒相容的,如與前面的 Vector 和 HashTable相對應的集合類 ArrayList 和 HashMap 等。

6.2.5 執行緒對立

執行緒對立是指無論呼叫端是否採取了同步措施,都無法在多執行緒環境中併發使用的程式碼。由於Java 語言天生就具備多執行緒特性,執行緒對立這種排斥多執行緒的程式碼是很少出現的,而且通常都是有害的,應當儘量避免。



相關文章