本系列將對Java多線程進行簡單的介紹。
分爲上中下三個章節。
上篇對操做系統中關於進程、併發的相關概念以及問題進行了介紹;
中篇對Java多線程的基礎進行介紹;
下篇將會對Java多線程編程提供的工具、模式進行介紹;
Java多線程,首先須要瞭解線程,瞭解線程又須要對進程有所瞭解,而瞭解進程你須要知道程序的概念,知道程序的概念,你還須要瞭解操做系統。
線程與操做系統
操做系統是對計算機硬件資源的管理程序,是應用程序與計算機硬件交互的中間層,其本質仍舊是運行於硬件電路上的程序
對計算機硬件來講不存在操做系統,只是處理器對指令的執行,不過操做系統是一個特殊一點的程序。
而對於應用開發者來講,以JavaWeb爲例,咱們卻接觸了太多的東西,首先是Java語言自己,而後...........
servlet?jsp?MVC?Spring?SpringBoot?ORM?Mybatis?Dubbo?
然而,這些其實仍舊仍是Java自己--Java語言編寫的程序,縱然有那麼多的規範,協議,他也只是一個Java編寫的程序
因此無論你用了多少技術,框架,模式,實現了怎麼樣的協議與功能,原理是什麼,也只是人類意識層面上的內容,到底層只有指令。
用到的一些應用軟件,MYSQL?REDIS?也只是程序。
因此,運行於計算機之上的這一切都只是程序
這些程序通過指定的步驟,從高級到低級,從人類能夠理解到沒法識別,最終轉換爲計算機能夠識別的指令。
咱們編寫的全部的源代碼,最終都要轉換成計算機系統能夠識別的內容,而計算機系統包括硬件以及運行其上的系統軟件。
咱們全部的編碼,都是面向指定的語法,而這門語言自己,則是面向操做系統的,由於外部軟件一般是不能直接操縱硬件資源,須要藉助於操做系統。
因此某種程度上能夠這樣認爲,全部的源代碼都是面向語言的,而語言自己面向操做系統。
操做系統提供了對於計算機硬件資源的管理,對於這些資源的訪問,提供了一系列的方法途徑,這些途徑方法如同機器的操做面板,如同駕駛艙的按鈕手柄。
因此說,計算機有什麼不重要,計算機操做系統有什麼才重要。最簡單的例子就是重裝系統後,若是沒有網卡驅動,你的電腦將沒法瞭解Internet,儘管你的網卡就好端端的插在哪裏。
對絕大多數應用程序員來講,操做系統,即是神同樣的存在,全部的一切都要仰仗於他。
什麼是程序?
遵循某種語言的源代碼通過編譯、翻譯等步驟轉換後的一組計算機能識別和執行的指令,這就是程序。
這是一種靜態的資源,當你的電腦中安裝一個軟件後,若是不啓動軟件,該軟件僅僅是佔用磁盤空間
一個程序就像一個用漢字(程序設計語言)寫下的紅燒肉菜譜(程序),用於指導懂漢語和烹飪手法的人來作這個菜。菜譜就是存在於紙上的文字。
當程序須要運行時,操做系統會加載該程序的信息到內存中,而且分配CPU時間片以及其餘硬件硬件資源,而且會對這些資源進行管理,好比數據加載到內存的什麼位置了?
並且,現代操做系統均可以同時併發執行多個程序,內存中的這些數據又都是哪一個程序的?某個軟件在進行切換時執行到哪裏了?等等這些都須要操做系統進行管理
操做系統將程序的一次運行抽象爲進程
簡言之,若是 你(處理器)按照 菜譜(程序)去 作菜(執行程序),這個過程就叫作 下廚作飯(進程)
抽象的概念,沒有人會陌生,若是咱們想使用Java語言描述一個學生,咱們可能會建立一個Student類,裏面有各類屬性,好比姓名、年齡等
public class Student {
private Long id;// id
private String name;// 姓名
private Integer age;// 年齡
private String sex;// 性別
//.............等等
這樣一個Class就是一個數據結構,經過他對學生進行描述
而進程是操做系統對程序的一次執行的抽象,也就是說一個程序運行須要哪些信息、數據?這些全部的數據項集合就叫作進程。簡言之就是一個程序運行所需信息的描述集合。
咱們以類來比喻的話多是這樣子:
public class 進程 {
private Long 進程號;
private String 程序計數器PC;
private String xxx寄存器;
private String 堆棧內容;
// ............................等等
}
還有一個概念是
進程上下文,
剛纔說到現代系統還能夠併發的執行多道程序,必然存在着CPU的切換,那麼從一個程序切換到另外一個程序時,如何纔可以恢復?
既然進程是程序的一次運行過程當中所須要信息的集合,若是在切換時,將這一瞬時狀態,這一集合體各項數據記錄下來,當再次切換回來時,只須要將數據恢復不就行了嗎
進程執行活動全過程的這一個靜態描述叫作進程上下文
進程間的切換,也被稱之爲上下文切換。
通俗比喻:
若是隻有一個廚房,你作菜作一半了,而後須要讓出來廚房讓別人作,你須要作什麼?
收拾好你的食材,記住你剛纔食材放置的位置以及處理的進度,哪一個菜洗過了?鹽放過了麼?。。。等等這些數據就是進程上下文,當別人撤出去以後,你須要將這些狀態還原,這就是上下文切換。
隨着現代計算機技術的發展,進程的弊端開始出現,因爲進程是資源擁有者,建立、撤消與切換存在較大的時空開銷,所以須要引入輕型進程,線程就是輕量級的進程。
進程仍然是資源分配的基本單位,線程是程序執行的最小單位
線程的出現能夠理解爲計算機操做系統對於程序的執行進行了更加精細化的控制,將資源分配,程序運行進行了更加細緻的分工。
每一個線程都運行在進程的上下文中,共享一樣的代碼和全局數據,很顯然,多線程比多進程更容易共享數據。
總之,線程的出現是操做系統技術的發展,爲了更加細化分工,節省開銷的一種作法,是在進程的基礎上發展而來的。
併發與並行
下面這幅圖能夠很好地解釋併發與並行
一個咖啡機兩個隊伍,就是併發;兩個咖啡機,兩個隊伍,就是並行。
併發 concurrent ,
經過CPU調度算法,進行進程間的切換,也就是多任務執行,操做系統將CPU時間片分配給每一個進程,給人並行處理的感受
並行 parallel,
並行就是同時執行的意思,多個CPU或者多個機器同時執行一段處理邏輯,是真正的同時。
多線程
好久好久好久之前,操做系統以串行的方式運行,當正在執行的程序遇到阻塞操做,好比等待IO時,CPU空閒等待,極大地浪費了CPU
因此後來出現了多任務操做系統,能夠對程序進行切換,當遇到阻塞操做時,CPU能夠去執行另外的程序,提升了CPU的利用率
對於線程也是如此,
多線程技術至關因而應用程序內部的「多任務」。
就比如一個應用程序內部有多個線程,其中一個線程等待IO操做時,能夠切換執行其餘的線程,完成其餘的任務,因此對於多線程編寫的程序,看起來程序可以更快的完成。
因此剛纔說線程是操做系統對於程序運行過程的更加細緻的劃分與掌控,對於一個多線程程序,可以更加充分的利用CPU資源,看起來執行快了,是由於CPU的效率變高了,而不是程序的運行所需時間變少了
對於一個單CPU系統,對於多任務的實現就是併發,操做系統不斷地進行着切換,將時間片分配給不一樣的程序,以看起來像多個程序是共同運行的。
經過多線程,將一個應用程序自己拆解爲多任務,若是像上面說的某個線程等待IO致使阻塞,能夠執行其餘的線程任務,那麼將會提升CPU的利用率
可是若是是相似1+2+3+4......+N的計算呢?假設計算過程是均等的,這不會出現IO阻塞的狀況,每一次的運算都是相同的,CPU自己也沒有空閒等待的浪費,因此CPU利用率沒有上升,相反還會有線程切換維護的開銷,因此總體看性能或許略有降低。
因此說,
單核場景下,儘管多線程在有些場景下能夠提升CPU的利用率,可是對於單CPU系統(單核)系統,在有些場景下,反而會下降總體性能。
由於有的時候你並不能提升利用率;並且有的時候即便提升了利用率,若是提升的那一部分利用率,還不足以抵消作的那些不應作的事情的開銷,總體看並不必定是往好的方向發展。
很顯然,對於單CPU(單核)儘管有些場景多線程能夠提升利用率,可是有時也並不能,因此多線程編程並無強勢發展。
可是後來,CPU主頻的發展愈來愈緩慢,對於CPU主頻的升級,摩爾定律開始失效了,由於發展太快,集成電路愈來愈接近極限了。
既然縱向不能發展,人們老是有辦法的,開始橫向發展,再也不追究單核的計算速度,而是研究如何可以將多個獨立的計算單元整合到一個CPU中,也就是如今說的多核。
隨着技術的發展, 可以裝載的核心數目愈來愈多
對於多核CPU,可以真正的作到在同一瞬時,執行多個線程,是真正的並行。
因此很顯然,這種場景對於真正的並行,無論你的程序任務是什麼樣子的,對於多線程程序,必然可以提升程序的執行速度。
若是隻要一個老師輔導三個學生,你須要合理的安排時間任務,纔有可能提升總體的效率;可是若是三個學生對應着三個老師同時在輔導,總體的效率確定是提升的。
因此隨着多核CPU以及超線程技術的發展,多線程編程就顯得格外重要。
若是單核CPU的性能能夠無限制的快速提升,軟件開發者徹底不用關心多線程編程,一切交給CPU就行了
可是,目前的狀況倒是CPU的性能已經達到瓶頸,硬件在橫向發展,因此若是想要提升CPU的利用率,讓你的程序更快的執行,你將不得不面對多線程編程。
《實戰Java高併發程序設計》中提到:「頂級計算機科學家唐納德·爾文·克努斯(Donald Ervin Knuth ),如此評價這種狀況:
在我看來,這種現象(併發)或多或少是因爲硬件設計者己經機關用盡了致使的,他們將摩爾定律失效的責任推脫給軟件開發者。」
也說明了這個問題----
如今爲何要更加關注多線程技術?
多核場景以及超線程技術的發展下,不是你主動地想要去使用多線程技術,而是現有的硬件體系,想要得到更好地程序性能,你將不得不使用多線程技術進行編程。
當我處理器仍是隻能一個一個的來的時候,大家是否是多線程並無那麼重要
可是當我能夠瞬時同時處理多個線程的時候,若是你仍是隻有一個線程,你每一時刻也只會有一個線程在執行,可是別人-多線程程序,可能就是多個,因此你的程序的速度與別人相比怎麼樣?
儘管藉助於多線程技術,由於有線程切換等系統開銷,因此總共須要CPU作的事情,要大於單線程的時候;
可是CPU多核的並行處理能力以及CPU利用率的提升,將會大大的提升程序的總體效率
因此在多核時代,多線程是必需要考慮的問題。
總結
無論是進程仍是線程,都是操做系統對於程序執行的抽象描述,是相關數據:寄存器狀態、堆棧值等全部相關數據的集合。
經過進程的相關信息的維護管理,操做系統保障多道程序能夠順利的切換執行;
而對於多線程的應用程序,須要開發者對線程的數據等相關信息進行控制,以保證多線程間能夠正確的運行。
多線程共享進程資源,而有些資源是互斥的,並不能容許同時訪問,好比對計數器+1,若是臨界區代碼能夠同時訪問,可能兩我的同時過來,每一個人同時從1開始執行加1操做,結果倒是2,這顯然是不正確的
多線程編程須要解決的核心就是互斥資源的訪問以及如何高效的利用CPU。
保障資源的互斥訪問是爲了保證程序的正確性,不然再快的程序也沒有意義;若是編寫的程序很是的不合理,邏輯不清晰,反而可能會帶來性能問題,而不是提升效率。
因此多線程相關的技術的確很複雜,並且很是容易出錯,並且學習成本很高,可是,他終歸是爲了提升CPU的利用率的同時而且保障臨界資源的正確訪問。
做爲多線程編程人員,如同交警,你須要合理的指揮,提升路口的通行效率,盡最大可能緩解交通堵塞狀況,並且須要保證不能在你的指揮下還發生了交通事故或者形成了更大的擁堵;
這是兩個主要方面,就是前面提到的效率和互斥訪問。
另外路口我應該清場出來多大空間用來調度指揮?(鎖粒度)過幾分鐘這個方向的走,過幾分鐘那個方向的走(鎖時間)?我是輪流幾秒鐘切換下?仍是哪邊車多讓哪邊多走一會仍是怎麼樣(鎖偏向)?這些細節很是複雜繁瑣。
在將來的一段時間內,多線程編程模型是必然的趨勢,也是程序員必需要面對的一件事情,過去的單處理器系統,併發多是多餘的,可是今天,已經成爲了勢不可擋的趨勢。
隨着技術的發展,多線程的開發也在從複雜往簡單的方向演化(儘管如今仍舊看起來很複雜),隨後可能會慢慢地出現不少集成、封裝、框架等以讓多線程編程更加簡單
就如同EJB-Spring-SpringBoot的發展,企業級應用的開發過程一直在簡化,可是核心原理卻不斷的被封裝在深處,若是不瞭解底層,只會招式,永遠也打不出來有力的拳頭,因此建議你們儘量的深刻學習多線程
本系列文章做爲本身的學習記錄,從操做系統中關於進程線程併發的相關概念切入,開始介紹Java多線程編程。