在早期不包含操作系統(tǒng)的計(jì)算機(jī)中,程序都是單一的串行程序,從頭至尾只能執(zhí)行一個(gè)程序,并且這個(gè)程序訪問這個(gè)計(jì)算機(jī)的所有資源。然而,隨著技術(shù)的發(fā)展,操作系統(tǒng)出現(xiàn)了。它使得計(jì)算機(jī)程序有了進(jìn)程,線程的概念,每次可以運(yùn)行多個(gè)程序,并且不同的程序都在單獨(dú)的進(jìn)程中運(yùn)行。操作系統(tǒng)為各個(gè)獨(dú)立執(zhí)行的進(jìn)程分配各種資源,包括內(nèi)存,文件句柄,安全證書等。不同進(jìn)程之間通過系統(tǒng)本身的通信機(jī)制來交換數(shù)據(jù),如:套接字,信號(hào)處理器,共享內(nèi)存,信號(hào)量以及文件等。
操作系統(tǒng)支持多個(gè)程序同時(shí)執(zhí)行,原因主要有:
創(chuàng)新新互聯(lián),憑借十多年的網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)經(jīng)驗(yàn),本著真心·誠心服務(wù)的企業(yè)理念服務(wù)于成都中小企業(yè)設(shè)計(jì)網(wǎng)站有近千家案例。做網(wǎng)站建設(shè),選創(chuàng)新互聯(lián)。
資源利用率。如某個(gè)程序在等待一個(gè)耗時(shí)操作完成,那么在等待的同時(shí)可以運(yùn)行另外一個(gè)程序,這樣可以提高資源利用率。
公平性。如通過時(shí)間片的方式讓程序輪流占用計(jì)算機(jī)資源,而不是由一個(gè)程序從頭至尾運(yùn)行完,再進(jìn)行下一個(gè)。
便利性。編寫多個(gè)程序來計(jì)算多個(gè)任務(wù),必要時(shí)進(jìn)行通信。比只編寫一個(gè)程序來計(jì)算所有任務(wù)更加容易實(shí)現(xiàn)。
串行編程模型優(yōu)勢(shì)在于直觀性和簡單性,每次只做一件事直至完成,然后再做另外一件。然而很多情況下,這個(gè)串行模型并不理想。打個(gè)比方,我們想燒水泡茶然后看書,以串行的工作方式,我們必須等到水燒開了把茶泡好,才能去看書。而現(xiàn)實(shí)生活中,完全可以燒水的過程先去看書,然后等待水燒開在去泡茶。這也引出了計(jì)算機(jī)應(yīng)用程序用,同步和異步的概念。正是這些原因,促使進(jìn)程,線程的出現(xiàn)。
線程,也被稱為輕量級(jí)進(jìn)程?,F(xiàn)在大多數(shù)操作系統(tǒng)中,都是以線程為基本的調(diào)度單位,而不是進(jìn)程。一個(gè)進(jìn)程可以創(chuàng)建多個(gè)線程,并且這些線程會(huì)共享進(jìn)程范圍內(nèi)的資源。所以,多個(gè)線程如果沒有明確的協(xié)同機(jī)制,那么他們是獨(dú)立運(yùn)行的。同樣,這些線程都可以訪問進(jìn)程的變量,如果沒有明確的同步機(jī)制來協(xié)同對(duì)共享數(shù)據(jù)的訪問,那么當(dāng)一個(gè)線程正在使用某個(gè)變量時(shí),另外一個(gè)線程可能同時(shí)訪問這個(gè)變量,造成不可預(yù)測(cè)的結(jié)果。但是,每個(gè)線程都有各自的計(jì)數(shù)器,棧以及局部變量等。
如果使用得當(dāng),可以降低開發(fā),維護(hù)成本,提升性能。線程還可以降低代碼復(fù)雜度,使得代碼更容易編寫,閱讀和維護(hù)。在GUI 程序中,可以提高界面的響應(yīng)速度;在服務(wù)端程序中,可以提升資源利用率和吞吐率。
發(fā)揮多核處理器的強(qiáng)大能力。
建模的簡單性。將復(fù)雜且異步的工作流分解到各個(gè)線程運(yùn)行,在特定的同步位置進(jìn)行交互。
異步事件的簡化處理。某個(gè)線程的阻塞不影響其他線程的處理。
響應(yīng)更靈敏的用戶界面。使用特定線程來處理耗時(shí)操作,而不是放在UI主線程中處理。比如 Android App 耗時(shí)事件不能在 UI 線程處理,會(huì)影響 UI 響應(yīng)的流暢度。
Java 對(duì)線程的使用是一把雙刃劍。線程的優(yōu)勢(shì)我們都已經(jīng)知道,前提是我們能夠正確的編寫出安全的并發(fā)代碼。然而,由于開發(fā)人員的技術(shù)不足,并發(fā)潛在風(fēng)險(xiǎn)的不易察覺,都有可能讓我們的程序達(dá)不到預(yù)期的效果。所以,我們有必要了解一下并發(fā)風(fēng)險(xiǎn)這一方面的內(nèi)容。
多個(gè)線程的執(zhí)行順序,在沒有同步的情況下是不可預(yù)測(cè)的,甚至產(chǎn)生奇怪的結(jié)果。如下面這個(gè)序列生成類,多個(gè)線程同時(shí)獲取到的值可能是相同的。
1 public class UnsafeSequene{
2 private int value;
3 //返回一個(gè)唯一的數(shù)值
4 public int getNext(){
5 return value++;
6 }
7 }
遞增操作 value++,實(shí)際上他包含三個(gè)獨(dú)立的操作:
讀取 value 的值
將 value 加 1
將計(jì)算結(jié)果寫入 value
由于多個(gè)線程之間的操作交替執(zhí)行,所以可能發(fā)生兩個(gè)線程讀到相同的值。如下圖所示的 A 線程和 B 線程:
這里寫圖片描述
上圖說明的是一種常見的并發(fā)安全問題,稱為競(jìng)態(tài)條件(Race Condition)。由于多個(gè)線程共享相同的內(nèi)存地址空間,并且是并發(fā)運(yùn)行,因此可能會(huì)訪問或修改其他線程正在使用的變量。這種方式比其他線程間通信機(jī)制更容易實(shí)現(xiàn)數(shù)據(jù)共享,但他同樣也帶來了巨大的風(fēng)險(xiǎn):線程由于無法預(yù)料數(shù)據(jù)的變化而發(fā)生錯(cuò)誤。
幸運(yùn)的是,Java 提供了各種同步機(jī)制來協(xié)同這種訪問。上面的示例代碼,把它改成一個(gè)同步方法,就可以防止這種錯(cuò)誤的發(fā)生。
1public class UnsafeSequene{
2 private int value;
3 //返回一個(gè)唯一的數(shù)值
4 public synchronized int getNext(){
5 return value++;
6 }
7}
安全性的含義是,永遠(yuǎn)不發(fā)生糟糕的事情?;钴S性是,某件正確的事情最終會(huì)發(fā)生。當(dāng)某個(gè)操作無法繼續(xù)執(zhí)行下去時(shí),就會(huì)發(fā)生活躍性問題,比如程序代碼進(jìn)入死循環(huán)。所線程導(dǎo)致的死鎖,也是活躍性問題,比如線程 A 在等待線程 B 釋放其持有的資源,而線程 B 永遠(yuǎn)都不釋放改資源,那么,線程 A 就會(huì)永遠(yuǎn)的等待下去。
活躍性意味著某件正確的事情最終會(huì)發(fā)生,但卻不夠好。這就是性能問題,因?yàn)槲覀兺ǔOM_的事盡快發(fā)生。性能問題包括:服務(wù)時(shí)間過長,響應(yīng)不靈敏,吞吐率過低,資源消耗過高等。
良好的并發(fā)程序,線程能提高性能,但無論如何,總會(huì)帶來某種程度的運(yùn)行時(shí)開銷。
1.線程調(diào)度臨時(shí)掛起活躍線程并轉(zhuǎn)而運(yùn)行另一個(gè)線程時(shí),就會(huì)頻繁的出現(xiàn)上下文切換,帶來極大的開銷:保存和恢復(fù)執(zhí)行上下文,CPU 時(shí)間更多的花在線程調(diào)度而不是線程運(yùn)行上。
2.使用同步機(jī)制,往往會(huì)抑制某些編譯器優(yōu)化。
即使在程序中沒有顯示的創(chuàng)建線程,但在框架中仍可能會(huì)創(chuàng)建線程,因此在這些線程中調(diào)用的代碼同樣必須是線程安全的??蚣芡ㄟ^在框架線程中調(diào)用應(yīng)用程序代碼將并發(fā)性引入到程序中。在代碼中將不可避免地訪問應(yīng)用程序狀態(tài),因此所有訪問這些狀態(tài)的代碼路徑都必須是線程安全的。
下面給出的模塊都將在應(yīng)用程序之外的線程中調(diào)用應(yīng)用程序的代碼。
Timer
Servlet 和 JavaServer Page
遠(yuǎn)程調(diào)用方法
Swing 和 AWT
本文原創(chuàng)首發(fā)于微信公眾號(hào) [ 林里少年 ],歡迎關(guān)注第一時(shí)間獲取更新。
這里寫圖片描述