多線程是Java中不可避免的一個重要主體。從本章開始,咱們將展開對多線程的學習。接下來的內容,是對「JDK中新增JUC包」以前的Java多線程內容的講解,涉及到的內容包括,Object類中的wait(), notify()等接口;Thread類中的接口;synchronized關鍵字。html
注:JUC包是指,Java.util.concurrent包,它是由Java大師Doug Lea完成並在JDK1.5版本添加到Java中的。算法
在進入後面章節的學習以前,先對了解一些多線程的相關概念。
線程狀態圖編程
說明:
線程共包括如下5種狀態。
1. 新建狀態(New) : 線程對象被建立後,就進入了新建狀態。例如,Thread thread = new Thread()。
2. 就緒狀態(Runnable): 也被稱爲「可執行狀態」。線程對象被建立後,其它線程調用了該對象的start()方法,從而來啓動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
3. 運行狀態(Running) : 線程獲取CPU權限進行執行。須要注意的是,線程只能從就緒狀態進入到運行狀態。
4. 阻塞狀態(Blocked) : 阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的狀況分三種:
(01) 等待阻塞 -- 經過調用線程的wait()方法,讓線程等待某工做的完成。
(02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(由於鎖被其它線程所佔用),它會進入同步阻塞狀態。
(03) 其餘阻塞 -- 經過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。
5. 死亡狀態(Dead) : 線程執行完了或者因異常退出了run()方法,該線程結束生命週期。swift
這5種狀態涉及到的內容包括Object類, Thread和synchronized關鍵字。這些內容咱們會在後面的章節中逐個進行學習。
Object類,定義了wait(), notify(), notifyAll()等休眠/喚醒函數。
Thread類,定義了一些列的線程操做函數。例如,sleep()休眠函數, interrupt()中斷函數, getName()獲取線程名稱等。
synchronized,是關鍵字;它區分爲synchronized代碼塊和synchronized方法。synchronized的做用是讓線程獲取對象的同步鎖。
在後面詳細介紹wait(),notify()等方法時,咱們會分析爲何「wait(), notify()等方法要定義在Object類,而不是Thread類中」。緩存
補充:服務器
1.進程:指的是一次程序的完整運行。在這個運行的過程當中,內存、處理器、IO等資源操做都要爲這個進程服務。網絡
2.線程:線程是在進程的基礎上劃分了多個線程。一個進程能夠包含多個線程。線程是比進程更快的處理單元,並且所佔的資源也少。線程的存在離不開進程。進程消失了,線程必定會消失。數據結構
線程的優勢及成本(來自:https://home.cnblogs.com/u/swiftma)多線程
優勢併發
爲何要建立單獨的執行流?或者說線程有什麼優勢呢?至少有如下幾點:
- 充分利用多CPU的計算能力,單線程只能利用一個CPU,使用多線程能夠利用多CPU的計算能力。
- 充分利用硬件資源,CPU和硬盤、網絡是能夠同時工做的,一個線程在等待網絡IO的同時,另外一個線程徹底能夠利用CPU,對於多個獨立的網絡請求,徹底可使用多個線程同時請求。
- 在用戶界面(GUI)應用程序中,保持程序的響應性,界面和後臺任務一般是不一樣的線程,不然,若是全部事情都是一個線程來執行,當執行一個很慢的任務時,整個界面將中止響應,也沒法取消該任務。
- 簡化建模及IO處理,好比,在服務器應用程序中,對每一個用戶請求使用一個單獨的線程進行處理,相比使用一個線程,處理來自各類用戶的各類請求,以及各類網絡和文件IO事件,建模和編寫程序要容易的多。
成本
關於線程,咱們須要知道,它是有成本的。建立線程須要消耗操做系統的資源,操做系統會爲每一個線程建立必要的數據結構、棧、程序計數器等,建立也須要必定的時間。
此外,線程調度和切換也是有成本的,當有當量可運行線程的時候,操做系統會忙於調度,爲一個線程分配一段時間,執行完後,再讓另外一個線程執行,一個線程被切換出去後,操做系統須要保存它的當前上下文狀態到內存,上下文狀態包括當前CPU寄存器的值、程序計數器的值等,而一個線程被切換回來後,操做系統須要恢復它原來的上下文狀態,整個過程被稱爲上下文切換,這個切換不只耗時,並且使CPU中的不少緩存失效,是有成本的。
固然,這些成本是相對而言的,若是線程中實際執行的事情比較多,這些成本是能夠接受的,但若是隻是執行本節示例中的counter++,那相對成本就過高了。
另外,若是執行的任務都是CPU密集型的,即主要消耗的都是CPU,那建立超過CPU數量的線程就是沒有必要的,並不會加快程序的執行。
上下文切換
即便是單核處理器也支持多線程執行代碼,CPU經過給每一個線程分配CPU時間片來實現 這個機制。時間片是CPU分配給各個線程的時間,由於時間片很是短,因此CPU經過不停地切 換線程執行,讓咱們感受多個線程是同時執行的,時間片通常是幾十毫秒(ms)。
CPU經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個
任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠再加載這
個任務的狀態。因此任務從保存到再加載的過程就是一次上下文切換。
這就像咱們同時讀兩本書,當咱們在讀一本英文的技術書時,發現某個單詞不認識,因而
便打開中英文字典,可是在放下英文技術書以前,大腦必須先記住這本書讀到了多少頁的第
多少行,等查完單詞以後,可以繼續讀這本書。這樣的切換是會影響讀書效率的,一樣上下文
切換也會影響多線程的執行速度。
多線程必定快嗎
當併發執行累加操做不超過百萬次時,速度會比串行執行累加操做要
慢。那麼,爲何併發執行的速度會比串行慢呢?這是由於線程有建立和上下文切換的開銷。
如何減小上下文切換
減小上下文切換的方法有無鎖併發編程、CAS算法、使用最少線程和使用協程。
·無鎖併發編程:多線程競爭鎖時,會引發上下文切換,因此多線程處理數據時,能夠用一 些辦法來避免使用鎖,如將數據的ID按照Hash算法取模分段,不一樣的線程處理不一樣段的數據。
·CAS算法:Java的Atomic包使用CAS算法來更新數據,而不須要加鎖。
·使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少線程來處理,這
樣會形成大量線程都處於等待狀態。
·協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換。
Java併發機制的底層實現原理
Java代碼在編譯後會變成Java字節碼,字節碼被類加載器加載到JVM裏,JVM執行字節 碼,最終須要轉化爲彙編指令在CPU上執行,Java中所使用的併發機制依賴於JVM的實現和 CPU的指令。
參考文獻:
http://www.cnblogs.com/skywang12345/p/3479024.html
《Java併發編程的藝術》