作業系統與程式執行以及程序簡介 多執行緒上篇(一)
本系列將對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多執行緒程式設計。