跳到主要內容

多線程與高併發(一)多線程入門

一、基礎概念


多線程的學習從一些概念開始,進程和線程,併發與并行,同步與異步,高併發。


1.1 進程與線程


幾乎所有的操作系統都支持同時運行期多個任務,所有運行中的任務通常就是一個進程,進程是處於運行過程中的程序,進程是操作系統進行資源分配和調度的一個獨立單位。



進程有三個如下特徵:



  • 獨立性:進程是系統中獨立存在的實體,它可以擁有自己獨立的資源,每一個進程都擁有自己私有的地址空間。在沒有經過進程本身允許的情況下,一個用戶進程不可以直接訪問其他進程的地址空間。


  • 動態性:進程與程序的區別在於,程序只是一個靜態的指令集合,而進程是一個正在系統中活動的指令集合。在進程中加入了時間的概念,進程具有自己的生命周期和各種不同的狀態,這些概念在程序中部是不具備的。


  • 併發性:多個進程可以在單個處理器上併發執行,多個進程之間不會互相影響。



線程是進程的組成部分,一個進程可以擁有多個線程,而線程必須有一個父進程,線程可以有自己的堆棧、自己的程序計數器和自己的局部變量,但不擁有系統資源。比如使用QQ時,我們可以同事傳文件,發送圖片,聊天,這就是多個線程在進行。


線程可以完成一定的任務,線程能夠獨立運行的,它不知道有其他線程的存在,線程的執行是搶佔式的,當前線程隨時可能被掛起。


總之:一個程序運行后至少有一個進程,一個進程里可以有多個線程,但至少要有一個線程。


1.2 併發和并行


併發和并行是比較容易混淆的概念,他們都表示兩個或者多個任務一起執行,但併發側重多個任務交替執行,同一時刻只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。而并行確實真正的同時執行,有多條指令在多個處理器上同時執行,并行的前提條件就是多核CPU。


1.3 同步和異步


同步和異步通常用來形容一次方法調用。同步方法調用一旦開始,調用者必須等到方法調用返回后,才能繼續後續的行為。異步方法調用更像一個消息傳遞,一旦開始,方法調用就會立即返回,調用者可以繼續後續的操作。


1.4 高併發


高併發一般是指在短時間內遇到大量操作請求,非常具有代表性的場景是秒殺活動與搶票,高併發是互聯網分佈式系統架構設計中必須考慮的因素之一,高併發相關常用的一些指標有響應時間(Response Time),吞吐量(Throughput),每秒查詢率QPS(Query Per Second),併發用戶數等。


多線程在這裏只是在同/異步角度上解決高併發問題的其中的一個方法手段,是在同一時刻利用計算機閑置資源的一種方式


1.5 多線程的好處


線程在程序中是獨立的、併發的執行流,擁有獨立的內存單元,多個線程共享父進程里的全部資源,線程共享的環境有進程的代碼段,進程的公有數據等,利用這些共享數據,線程很容易實現相互之間的通信,可以提高程序的運行效率。


多線程的好處主要有:



  • 進程之間不能共享內存,但線程之間共享內存非常容易。


  • 系統創建進程時需要給進程重新分配系統資源,但創建線程代價小得多,所以使用多線程實現多任務併發比多進程效率高


  • Java語言內置了多線程功能支持。



二、使用多線程


上面講了多線程的一些概念,都有些抽象,下面將學習如何使用多線程,創建多線程的方式有三種。


2.1 繼承Thread類創建


繼承Thread創建並啟動多線程有三個步驟:



  1. 定義類並繼承Thread,重寫run()方法,run()方法中為需要多線程執行的任務。


  2. 創建該類的實例,即創建了線程對象。


  3. 調用實例的start()方法啟動線程。



public class FirstThread extends Thread { 

private int i=0;
public void run() {
for (; i < 100; i++) {
//獲取當前線程名稱
System.out.println(this.getName() + " " + i);
}
}

public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
//Thread的靜態方法currentThread,獲取當前線程
System.out.println(Thread.currentThread().getName());
if (i == 20) {
//創建線程並啟動
new FirstThread().start();
new FirstThread().start();
}

}
}
}

運行結果可以看到兩個線程的i並不是連續的,說明他們並不共享數據。


2.2 實現Runnable接口


實現Runnable接口創建並啟動多線程也有以下步驟:



  1. 定義類並繼承Runnable接口,重寫run()方法,run()方法中為需要多線程執行的任務。


  2. 創建該類的實例,並以此實例作為target為參數來創建Thread對象,這個Thread對象才是真正的多線程對象。



public class SecondThread implements Runnable { 
private int i = 0;

@Override
public void run() {
for (; i < 100; i++) {
//此時想要獲取到多線程對象,只能使用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName() + " " + i);
}
}

public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
//Thread的靜態方法currentThread,獲取當前線程
System.out.println(Thread.currentThread().getName());
if (i == 20) {
//創建線程並啟動
SecondThread secondThread=new SecondThread();
new Thread(secondThread,"線程一").start();
new Thread(secondThread,"線程二").start();
}

}
}
}

2.3 使用Callable和Future


Callable是Runnable的增加版,主要是接口中的call()方法可以有返回值,並且可以申明拋出異常,使用Callable創建的步驟如下:



  1. 定義類並繼承Callable接口,重寫call()方法,run()方法中為需要多線程執行的任務。


  2. 創建類實例,使用FutureTask來包裝對象實例,


  3. 使用FutureTask對象作為Thread的target來創建多線程,並啟動線程。


  4. 調用FutureTask對象的get()方法來獲取子線程結束后的返回值。



public class ThirdThread { 

public static void main(String[] args) {
//使用lambda表達式
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>) () -> {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ "的循環變量i的值:" + i);
}
return i;
});
for (int i = 0; i < 100; i++) {
//Thread的靜態方法currentThread,獲取當前線程
System.out.println(Thread.currentThread().getName());
if (i == 20) {
//創建線程並啟動
new Thread(task, "有返回值的線程").start();
}
}
try {
System.out.println(
"線程的返回值:" + task.get());
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (ExecutionException e) {
e.printStackTrace();
}
}
}

這裏使用了lambda表達式,不使用表達式的方式也很簡單,可以去源碼中查看。Callable與Runnable方式基本相同,只不過增加了返回值且可允許聲明拋出異常。


使用三種方式都可以創建線程,且方式也相對簡單,大體分為實現接口和實現Thread類兩種,這兩種都各有優缺點。


繼承接口實現:



  • 優點:除了繼承接口之外,還可以繼承其他類。這種方式多個線程共享一個target對象,可以處理用於共同資源的情況。

  • 缺點:編程稍微複雜一些,並且沒有直接獲取當前線程對象的方式,必須使用Thread.currentThread()方式。


基礎Thread類:



  • 優點:編程簡單


  • 缺點:不能繼承其他類



三、多線程的生命周期


線程狀態是線程中非常重要的一個概念,然而我看過很多資料,線程的狀態理解有很多種方式,很多人將其分為五個基本狀態:新建、就緒、運行、阻塞、死亡,但在狀態枚舉中並不是這五個狀態,我不知道是什麼原因(有大神可以解答更好),只能按照枚舉中的狀態根據自己的理解。



  1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法,而且就算調用了改方法也不代表狀態立即改變。


  2. 運行(RUNNABLE):在運行的狀態肯定就處於RUNNABLE狀態。


  3. 阻塞(BLOCKED):表示線程阻塞,或者說線程已經被掛起了。


  4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。


  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間后自行返回。


  6. 終止(TERMINATED):表示該線程已經執行完畢。



狀態流程圖如下:



理解:初始狀態很好理解,這個時候其實還不能被稱為一個線程,因為他還沒被啟動,當調用start()方法后,線程正式啟動,但是也不代表立即就改變了狀態。


運行狀態中其實包含兩種狀態,運行中(RUNING)就緒(READY)


就緒狀態表示你有資格運行,只要CPU還未調度到你,就處於就緒狀態,有幾個狀態會是線程狀態編程就緒狀態



  • 調用線程的start()方法。


  • 當前線程sleep()方法結束,其他線程join()結束,等待用戶輸入完畢,某個線程拿到對象鎖。


  • 當前線程時間片用完了,調用當前線程的yield()方法。


  • 鎖池裡的線程拿到對象鎖后。



運行中(RUNING)狀態比較好理解,線程調度程序選擇了當前線程作。


阻塞狀態是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。


等待狀態是指線程沒有被CPU分配執行時間,需要等待,這種等待是需要被显示的喚醒,否則會無限等待下去。


超時等待狀態是這現在沒有被CPU分配執行時間,需要等待,不過這種等待不需要被显示的喚醒,會設置一定的時間后zi懂喚醒。


死亡狀態也很好理解,說明線程方法被執行完成,或者出錯了,線程一旦進入這個狀態就代表徹底的結束


 

【精選推薦文章】



智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選



想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計



帶您來看台北網站建置台北網頁設計,各種案例分享



廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益



Orignal From: 多線程與高併發(一)多線程入門

留言

這個網誌中的熱門文章

Python 併發總結,多線程,多進程,異步IO

1 測量函數運行時間 import time def profile(func): def wrapper(*args, ** kwargs): import time start = time.time() func( *args, ** kwargs) end = time.time() print ' COST: {} ' .format(end - start) return wrapper @profile def fib(n): if n<= 2 : return 1 return fib(n-1) + fib(n-2 ) fib( 35 )   2 啟動多個線程,並等待完成   2.1 使用threading.enumerate() import threading for i in range(2 ): t = threading.Thread(target=fib, args=(35 ,)) t.start() main_thread = threading.currentThread() for t in threading.enumerate(): if t is main_thread: continue t.join()   2.2 先保存啟動的線程 threads = [] for i in range(5 ): t = Thread(target=foo, args= (i,)) threads.append(t) t.start() for t in threads: t.join()   3 使用信號量,限制同時能有幾個線程訪問臨界區 from threading import Semaphore import time sema = Semaphor...

高雄十大包子名店出爐

, 圖文:吳恩文 高雄包子大賽落幕了,我只能就我個人意見, 介紹一下前十名這些包子,但是不能代表其他四位評審的意見,雖然身為評審長,我通常不會第一個表示意見,以免影響其他評審, 我主要工作是負責發問。   這次參賽的素包子很少,而且都不夠細致,又偏油,我不愛, 但是第一名的甜芝麻包-熔岩黑金包,竟然是素食得名- 漢來蔬食巨蛋店。   這包子賣相太好,竹炭粉的黑色外皮刷上金粉,一上桌,眾人驚呼, 搶拍照,內餡是芝麻餡,混一點花生醬增稠,加入白糖芝麻油, 熔岩爆漿的程度剛剛好,我一直以為芝麻要配豬油才行、 但是選到好的黑芝麻油一樣不減香醇, 當下有二位評審就想宅配回家。   尤其特別的是,黑芝麻餡室溫易化,師傅必須要輪班躲在冷藏室內, 穿著大外套才能包,一天包不了多少,我笑說,漢來美食,集團餐廳那麼多,實力雄厚,根本是「 奧運選手報名參加村裡運動會」嘛,其他都是小包子店啊, 但是沒辦法,顯然大家都覺得它好看又好吃, 目前限定漢來蔬食高雄巨蛋店,二顆88元,可以冷凍宅配, 但是要排一陣子,因為供不應求,聽說,四月份, 台北sogo店開始會賣。   第二名的包子,左營寬來順早餐店,顯然平易近人的多,一顆肉包, 十塊錢,是所有參賽者中最便宜的,當然,個頭也小, 它的包子皮明顯和其他不同,灰灰的老麵,薄但紮實有嚼勁, 肉餡新鮮帶汁,因為打了些水,味道極其簡單,就是蔥薑,塩, 香油,薑味尤其明顯,是老眷村的味道, 而特別的是老闆娘是台灣本省人, 當年完全是依據眷村老兵的口味一步一步調整而來,沒有加什麼糖、 五香粉,胡椒粉,油蔥酥。就是蔥薑豬肉和老麵香,能得名, 應該是它的平實無華,鮮美簡單,打動人心。   這是標準的心靈美食,可以撫慰人心,得名之前,寛來順已經天天排隊,現在,恐怕要排更久了, 建議大家六七點早點上門。   第三名,「專十一」很神奇,我記得比賽最後, 大家連吃了幾家不能引起共鳴的包子,有些累,到了專十一, 就坐著等包子,其他評審一吃,就催我趕快試,我一吃, 也醒了大半。   它的包子皮厚薄適中,但是高筋麵粉高些,老麵加一點點酵母, 我心中,它的皮屬一屬二,至於餡又多又好吃,蛋黃還是切丁拌入, 不是整顆放,吃起來「美味、均衡、飽滿」。一顆二十元。   老闆是陸軍專科十一期畢業取名專十一,...

韋伯連續劇終於更新 期待第一季順利完結

  地球天文學界的跳票大王詹姆斯·韋伯空間望遠鏡 (James Webb Space Telescope,縮寫為 JWST)自 1996 年以來斷斷續續不按劇本演出的連續劇終於讓焦慮的觀眾們又等到了一次更新:五層遮陽罩測試順利完成。 裝配完成的韋伯望遠鏡與好夥伴遮陽罩同框啦。Credit: NASA   嚴格的測試是任何空間任務順利成功的重中之重。遮陽罩,這個韋伯望遠鏡異常重要的親密夥伴,要是無法正常運轉的話,韋伯的這一季天文界連續劇說不準就要一直拖更了。   詹姆斯·韋伯空間望遠鏡是歷史上造出的最先進的空間望遠鏡。它不僅是一架紅外望遠鏡,還具有特別高的靈敏度。但想要達到辣么高的靈敏度來研究系外行星和遙遠的宇宙童年,韋伯童鞋必須非常"冷靜",體溫升高的話,靈敏度會大大折損。這個時候,遮陽罩就要大顯身手啦。   遮陽罩在韋伯的設計中至關重要。韋伯望遠鏡會被發射到拉格朗日 L2 點,運行軌道很高,遠離太陽、地球與月球。太陽是韋伯的主要熱量干擾的來源,其次是地球與月球。遮陽罩會有效阻斷來自這三大熱源的能量並保護韋伯維持在工作溫度正常運轉。這個工作溫度指的是零下 220 攝氏度(-370 華氏度;50 開爾文)。 上圖中我們可以看出,韋伯望遠鏡的配置大致可分為兩部分:紅色較熱的一面溫度為 85 攝氏度,藍色較冷的一面溫度達到零下 233 攝氏度。紅色的這部分中,儀器包括太陽能板、通信設備、計算機、以及轉向裝置。藍色部分的主要裝置包括鏡面、探測器、濾光片等。Credit: STSci.   遮陽罩的那一部分和望遠鏡的鏡面這部分可以產生非常極端的溫差。遮陽的這面溫度可以達到 110 攝氏度,足以煮熟雞蛋,而背陰處的部分溫度極低,足以凍結氧氣。   工程師們剛剛完成了五層遮陽罩的測試,按照韋伯在 L2 時的運行狀態安裝了遮陽罩。L2 距離地球約 160 萬公里。NASA 表示這些測試使用了航天器的自帶系統來展開遮陽罩,測試目前都已成功完成。韋伯望遠鏡遮陽罩負責人 James Cooper 介紹說這是遮陽罩"第一次在望遠鏡系統的电子設備的控制下展開。儘管這個任務非常艱巨,難度高,但測試順利完成,遮陽罩展開時的狀態非常驚艷"。   遮陽罩由五層 Kapton 製成。Kapton 是一種聚酰亞胺薄膜材料, 耐高溫絕...