Java多線程(一)多線程入門篇

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)。



3 多線程的使用方式

3.1 繼承 Thread類



能夠看到程序在交替執行,可是最終都會執行到98。

3.2 實現Runnable接口




依舊能夠看到程序在交替執行,可是最終都會執行到98。

3.3 繼承實現Callable接口



這裏須要說明的是實現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 語言天生就具有多線程特性,線程對立這種排斥多線程的代碼是不多出現的,並且一般都是有害的,應當儘可能避免。

相關文章
相關標籤/搜索