Java 基礎--線程

概念html

提到線程(thread),就不得不談談進程(process),將二者首先從概念上區分出來。java

操做系統中的進程是資源的組織單位。進程有一個包含了程序內容和數據的地址空間,以及其餘資源,包括打開的文件、子進程和信號處理器等。不一樣進程的地址空間是相互隔離的。算法

線程表示的是程序的執行流程,是CPU調度的基本單位。線程有本身的程序計數器、寄存器、棧和幀等。引入線程的動機在於操做系統中阻塞式 I/O的存在。當一個線程所執行的 I/O被阻塞的時候,同一進程中的其餘線程可使用CPU來進行計算。這樣的話,就提升了應用的執行效率。線程的概念在主流的操做系統和編程語言中都獲得了支持編程

Java線程的建立api

一部分的java程序是單線程的。程序的機器指令按照程序中給定的順序依次執行。Java語言提供了java.lang.Thread類來爲線程提供抽象。有兩種方式建立一個新的線程:一種是繼承java.lang.Thread類並覆寫其中的run()方法,另外一種則是在建立java.lang.Thread類的對象的時候,在構造函數中提供一個實現了java.lang.Runnable接口的類對象。在獲得了java.lang.Thread類的對象以後,經過調用其start()方法就能夠啓動這個線程的執行。緩存

一個線程被建立成功並啓動以後,能夠處在不一樣的狀態中。這個線程可能正在佔用CPU時間運行;也可能處在就緒狀態,等待被調度執行;還可能阻塞在某個資源或是事件上。多個就緒狀態的線程會競爭CPU時間以得到被執行的機會,而CPU則採用某種算法來調度線程的執行。不一樣線程的運行順序是不肯定的,多線程程序中的邏輯不能依賴於CPU的調度算法。性能優化

可見性多線程

  可見性(visibility)的問題是Java多線程應用中的錯誤的根源。在一個單線程程序中,若是首先改變一個變量的值,再讀取該變量的值的時候,所讀取到的值就是上次寫操做寫入的值。也就是說前面操做的結果對後面的操做是確定可見的。可是在多線程程序中,若是不使用必定的同步機制,就不能保證一個線程所寫入的值對另一個線程是可見的。形成這種狀況的緣由可能有下面幾個:架構

  • CPU 內部的緩存:如今的CPU通常都擁有層次結構的幾級緩存。CPU直接操做的是緩存中的數據,並在須要的時候把緩存中的數據與主存進行同步。所以在某些時刻,緩存中的數據與主存內的數據多是不一致的。某個線程所執行的寫入操做的新值可能當前還保存在CPU的緩存中,尚未被寫回到主存中。這個時候,另一個線程的讀取操做讀取的就仍是主存中的舊值。oracle

  • CPU的指令執行順序:在某些時候,CPU可能改變指令的執行順序。這有可能致使一個線程過早的看到另一個線程的寫入操做完成以後的新值。

  • 編譯器代碼重排:出於性能優化的目的,編譯器可能在編譯的時候對生成的目標代碼進行從新排列。

現實的狀況是:不一樣的CPU可能採用不一樣的架構,而這樣的問題在多核處理器和多處理器系統中變得尤爲複雜。而Java的目標是要實現「編寫一次,處處運行」,所以就有必要對Java程序訪問和操做主存的方式作出規範,以保證一樣的程序在不一樣的CPU架構上的運行結果是一致的。Java內存模型(Java Memory Model)就是爲了這個目的而引入的。JSR 133則進一步修正了以前的內存模型中存在的問題。總得來講,Java內存模型描述了程序中共享變量的關係以及在主存中寫入和讀取這些變量值的底層細節。Java內存模型定義了Java語言中的synchronizedvolatilefinal等關鍵詞對主存中變量讀寫操做的意義。Java開發人員使用這些關鍵詞來描述程序所指望的行爲,而編譯器和JVM負責保證生成的代碼在運行時刻的行爲符合內存模型的描述。好比對聲明爲volatile的變量來講,在讀取以前,JVM會確保CPU中緩存的值首先會失效,從新從主存中進行讀取;而寫入以後,新的值會被立刻寫入到主存中。而synchronized和volatile關鍵詞也會對編譯器優化時候的代碼重排帶來額外的限制。好比編譯器不能把 synchronized塊中的代碼移出來。對volatile變量的讀寫操做是不能與其它讀寫操做一塊從新排列的。

更多關於Java內存模型請參考http://my.oschina.net/u/134516/blog/614806 

Java中的鎖

當數據競爭存在的時候,最簡單的解決辦法就是加鎖。鎖機制限制在同一時間只容許一個線程訪問產生競爭的數據的臨界區。Java語言中的 synchronized關鍵字能夠爲一個代碼塊或是方法進行加鎖。任何Java對象都有一個本身的監視器,能夠進行加鎖和解鎖操做。當受到 synchronized關鍵字保護的代碼塊或方法被執行的時候,就說明當前線程已經成功的獲取了對象的監視器上的鎖。當代碼塊或是方法正常執行完成或是發生異常退出的時候,當前線程所獲取的鎖會被自動釋放。一個線程能夠在一個Java對象上加屢次鎖。同時JVM保證了在獲取鎖以前和釋放鎖以後,變量的值是與主存中的內容同步的。

更多內容請參見:http://my.oschina.net/u/134516/blog/614809 

Java線程的同步

在有些狀況下,僅依靠線程之間對數據的互斥訪問是不夠的。有些線程之間存在協做關係,須要按照必定的協議來協同完成某項任務,好比典型的生產者-消費者模式。這種狀況下就須要用到Java提供的線程之間的等待-通知機制。當線程所要求的條件不知足時,就進入等待狀態;而另外的線程則負責在合適的時機發出通知來喚醒等待中的線程。Java中的java.lang.Object類中的wait/notify/notifyAll方法組就是完成線程之間的同步的。

  更多Java線程的同步請參考:http://my.oschina.net/u/134516/blog/614840 

中斷線程

經過一個線程對象的interrupt()方法能夠向該線程發出一箇中斷請求。中斷請求是一種線程之間的協做方式。當線程A經過調用線程B的interrupt()方法來發出中斷請求的時候,線程A 是在請求線程B的注意。線程B應該在方便的時候來處理這個中斷請求,固然這不是必須的。當中斷髮生的時候,線程對象中會有一個標記來記錄當前的中斷狀態。經過isInterrupted()方法能夠判斷是否有中斷請求發生。若是當中斷請求發生的時候,線程正處於阻塞狀態,那麼這個中斷請求會致使該線程退出阻塞狀態。可能形成線程處於阻塞狀態的狀況有:當線程經過調用wait()方法進入一個對象的等待集合中,或是經過sleep()方法來暫時休眠,或是經過join()方法來等待另一個線程完成的時候。在線程阻塞的狀況下,當中斷髮生的時候,會拋出java.lang.InterruptedException,代碼會進入相應的異常處理邏輯之中。實際上在調用wait/sleep/join方法的時候,是必須捕獲這個異常的。中斷一個正在某個對象的等待集合中的線程,會使得這個線程從等待集合中被移除,使得它能夠在再次得到鎖以後,繼續執行java.lang.InterruptedException異常的處理邏輯。

經過中斷線程能夠實現可取消的任務。在任務的執行過程當中能夠按期檢查當前線程的中斷標記,若是線程收到了中斷請求,那麼就能夠終止這個任務的執行。當遇到 java.lang.InterruptedException的異常,不要捕獲了以後不作任何處理。若是不想在這個層次上處理這個異常,就把異常從新拋出。當一個在阻塞狀態的線程被中斷而且拋出java.lang.InterruptedException異常的時候,其對象中的中斷狀態標記會被清空。若是捕獲了java.lang.InterruptedException異常可是又不能從新拋出的話,須要經過再次調用interrupt()方法來從新設置這個標記。

相關文章
相關標籤/搜索