線程相關概念

當多個線程對同一個共享變量/對象進行操作,即使是最簡單的操作,比如i++,在處理上實際也,涉及到讀取、自增、賦值這三個操作,也就是說,這中間可能存在時間差,導致多個線程沒有按照程序編寫者所期望的去順序執行,出現錯位,從而導致最終結果與預期的不一致。

java中的多線程同步是通過鎖的概念來體現的,鎖不是一個對象,也不是一個具體的東西,而是一種機制的名稱。鎖機制需要保證如下兩種特性:

  • 互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(複合操作)進行訪問,互斥性我們也往往稱之爲操作的原子性。
  • 可見性:必須必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。

 

掛起、休眠、阻塞與非阻塞

掛起(Suspend):當線程被掛起的時候,其會失去CPU的使用時間,直到被其他線程(用戶線程或調度線程)喚醒。

休眠(Sleep):同樣是會失去CPU的使用時間,但是在過了指定的休眠時間之後,它會自動激活,無需喚醒(整個喚醒表面看是自動的,但實際上也得有守護線程去喚醒,只是不需編程者手動干預)。

阻塞(Block):在線程執行時,所需要的資源不能得到,則線程被掛起,直到滿足可操作的條件。

非阻塞(Block):在線程執行時,所需要的資源不能得到,但線程不是被掛起等待,而是繼續執行其餘事情,待條件滿足了之後,收到了通知(同樣是守護線程去做)再執行。

掛起和休眠是獨立的操作系統的概念,而阻塞與非阻塞則是在資源不能得到時的兩種處理方式,不限於操作系統,當資源申請不到時,要麼掛起線程等待、要麼繼續執行其他操作,資源被滿足後再通知該線程重新請求。顯然非阻塞的效率要高於阻塞,相應的實現的複雜度也要高一些。

在Java中顯式的掛起之前是通過Thread的suspend方法來體現,現在此概念已經消失,原因是suspend/resume方法已經被廢棄,它們容易產生死鎖,在suspend方法的註釋裏有這麼一段話:當suspend的線程持有某個對象鎖,而resume它的線程又正好需要使用此鎖的時候,死鎖就產生了

所以,現在的JDK版本中,掛起是JVM的系統行爲,程序員無需干涉。休眠的過程中也不會釋放鎖,但它一定會在某個時間後被喚醒,所以不會死鎖。現在我們所說的掛起,往往並非指編寫者的程序裏主動掛起,而是由操作系統的線程調度器去控制。

相應地有必要提下java.lang.Object的wait/notify,這兩個方法同樣是等待/通知,但它們的前提是已經獲得了鎖,且在wait(等待)期間會釋放鎖。在wait方法的註釋裏明確提到:線程要調用wait方法,必須先獲得該對象的鎖,在調用wait之後,當前線程釋放該對象鎖並進入休眠(這裏到底是進入休眠還是掛起?文檔沒有細說,從該方法能指定等待時間來看,更可能是休眠,沒有指定等待時間的,則可能是掛起,不管如何,在休眠/掛起之前,JVM都會從當前線程中把該對象鎖釋放掉),只有以下幾種情況下會被喚醒:其他線程調用了該對象的notify或notifyAll、當前線程被中斷、調用wait時指定的時間已到。

 

內核態與用戶態

有一些系統級的調用,比如:清除時鐘、創建進程等這些系統指令,如果這些底層系統級指令能夠被應用程序任意訪問的話,那麼後果是危險的,系統隨時可能崩潰,所以 CPU將所執行的指令設置爲多個特權級別,在硬件執行每條指令時都會校驗指令的特權,比如:Intel x86架構的CPU將特權分爲0-3四個特權級,0級的權限最高,3權限最低。

而操作系統根據這系統調用的安全性分爲兩種:內核態和用戶態。內核態執行的指令的特權是0,用戶態執行的指令的特權是3。

  • 當一個任務(進程)執行系統調用而進入內核指令執行時,進程處於內核運行態(或簡稱爲內核態);
  • 當任務(進程)執行自己的代碼時,進程就處於用戶態。
  • 在執行系統級調用時,需要將變量傳遞進去、可能要拷貝、計數、保存一些上下文信息,然後內核態執行完成之後需要再將參數傳遞到用戶進程中去,這個切換的代價相對來說是比較大的,所以應該是儘量避免頻繁地在內核態和用戶態之間切換。

Java並沒有自己的線程模型,而是使用了操作系統的原生線程!

線程方面的事在操作系統來說屬於系統級的調用,需要在內核態完成,所以如果頻繁地執行線程掛起、調度,就會頻繁造成在內核態和用戶態之間切換,影響效率。

JDK5之前的synchronized效率低下,是因爲在阻塞時線程就會被掛起、然後等待重新調度,而線程操作屬於內核態,這頻繁的掛起、調度使得操作系統頻繁處於內核態和用戶態的轉換,造成頻繁的變量傳遞、上下文保存等,從而性能較低。

 

Main線程

main線程是個非守護線程,不能設置爲守護線程。

這是因爲,Main線程是由Java虛擬機在啓動的時候創建的。main方法開始執行的時候,主線程已經創建好並在運行了。對於運行中的線程,調用Thread.setDaemon()會拋出異常Exception in thread "main" java.lang.IllegalThreadStateException。

Main線程結束,其他線程一樣可以正常運行

主線程,只是個普通的非守護線程,用來啓動應用程序,不能設置成守護線程;除此之外,它跟其他非守護線程沒有什麼不同。主線程執行結束,其他線程一樣可以正常執行。線程其實並不存在互相依賴的關係,一個線程的死亡從理論上來說,不會對其他線程有什麼影響。

Main線程結束,其他線程也可以立刻結束,當且僅當這些子線程都是守護線程

Java虛擬機(相當於進程)退出的時機是:虛擬機中所有存活的線程都是守護線程。只要還有存活的非守護線程虛擬機就不會退出,而是等待非守護線程執行完畢;反之,如果虛擬機中的線程都是守護線程,那麼不管這些線程的死活java虛擬機都會退出。

併發與並行

併發和並行的區別就是:一個處理器同時處理多個任務和多個處理器或者是多核的處理器同時處理多個不同的任務。前者是邏輯上的同時發生(simultaneous),而後者是物理上的同時發生。

  • 併發性(concurrency),又稱共行性,是指能處理多個同時性活動的能力,併發事件之間不一定要同一時刻發生。
  • 並行(parallelism)是指同時發生的兩個併發事件,具有併發的含義,而併發則不一定並行。

來個比喻:併發和並行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭。

 

單就一個CPU而言兩個線程可以解決線程阻塞造成的不流暢問題,其本身運行效率並沒有提高,多CPU的並行運算才真正解決了運行效率問題,這也正是併發和並行的區別。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章