對於多線程,我們更多地是在操作系統中接觸到這個概念,但是Java卻在語言級支持多線程,這也是Java語言的一大優勢。Java所使用的線程同步機制是監視器。
1. 監視器
Java監視器支持兩種線程同步:互斥和協作。
互斥:通過對象鎖來實現,允許多個線程在同一個共享數據上獨立而不干擾地工作。
協作:通過Object類的wait方法和notify方法來實現,允許多個線程爲了同一個目標而共同工作。
1.1 監視器的互斥
Java虛擬機爲每個對象分配一個監視器。當多個同步線程需要對同一對象進行操作或者執行該對象的同步方法的時候,它們之間必須競爭該對象的監視器。只有獲得該對象的監視器的線程才能執行,其他線程必須等待該線程釋放了該對象的監視器以後,再去競爭該對象的監視器。
監視器除了同步一些數據之外,還涉及到一些代碼,也就是所謂的監視區域。對一個監視器來說,監視區域是最小的,不可分割的代碼塊。同一個監視器中,監視區域只會被一個線程執行,線程只有獲得了監視區域的監視器才能執行。
當一個線程到達了一個監視區域的開始處,它就會放置到監視器的入口區。如果沒有其他線程等待,也沒有線程正持有該監視器,它就會獲得監視器。當執行完監視區域的代碼後,就會釋放並退出監視器。如果當前監視器正在被另外一個線程持有,那麼它就會進入一個等待隊列。當持有線程退出之後,等待隊列中的線程會競爭該監視器。最終只能有一個線程持有該監視器,其他的線程仍會留在等待隊列中。
1.2 監視器的協作
當一個線程需要一些特別的數據,而由另一個線程負責改變這些數據的狀態時,同步顯得特別重要。Java虛擬機用於同步的監視器叫“等待並喚醒”監視器。
這種監視器中,一個已經持有該監視器的線程,可以通過調用等待命令,暫停自身的執行。當這個線程執行等待命令後,就會釋放該監視器並進入一個等待區,並會一直在那裏持續等待狀態,直到一段時間後該監視器內的其他線程調用了喚醒命令。當一個線程調用喚醒命令後,它會持續持有監視器,直到它主動釋放監視器,如執行了一條等待命令或者執行完監視區域。當執行喚醒的線程釋放監視器後,等待線程會甦醒,其中的一個等待線程會重新獲得監視器。
2. JVM線程同步的實現
JVM爲每個對象和類都關聯一個監視器,在語言層次上用同步方法或者同步語句標識監視區域,每個對象都實現等待/通知方法等方式來實現線程同步。
2.1 對象鎖
JVM中的每個線程都有一個獨立的棧,它們之間互不干擾,所以無需同步棧中的數據。而每個JVM中只有一個堆和一個方法區,堆中的實例變量和方法區中的類變量會被所有線程所共享,所以需要進行同步。
JVM爲每個對象和類都關聯一個對象鎖,來通過監視器的機制實現對某個對象的互斥訪問和基於某條件的協作。對象的對象鎖針對的是java.lang.Object實例對象,而類的對象鎖針對的是java.lang.Class類對象。
JVM通過一個爲每個對象鎖分配一個計數器,來記錄對象被加鎖的次數。對象鎖具有如下特性:
—對象沒有被鎖,計數器爲0,訪問它的線程可以對它加鎖;
—該對象鎖已被某線程獲得,其他線程必須等待,直到該線程釋放對象鎖
—一個線程已經獲得對象鎖,則它可再次對該對象加鎖,每加鎖一次,計數器加1
—擁有對象鎖的線程釋放對象鎖時,計數器減1;直到計數器減爲0時,等待該鎖的線程競爭使用它
2.2 語言層次的線程同步
Java語言層次對線程同步的支持,主要包括對監視區域的標識和協作方法。
Java語言對監視區域的標識是通過同步方法和同步語句實現的。同步方法是在類方法前面用synchronized關鍵字聲明。同步語句是把需要同步的語句用synchronized關鍵字標識出並指名同步語句所針對的對象。
Java語言提供的協作方法有wait()、notify()和notifyAll()三個,它們都是java.lang.Object類的方法。由於Java語言的每個對象都是java.lang.Object的子類,因此所有對象都實現了線程協作的方法。