瞭解Java中線程和鎖的複雜性

多線程編程是多線程同步處理的結果。線程是此過程中的基本要素。與多線程相關的複雜性很多。在這裏,優銳課小U帶大家深入研究Java創建的線程創建,同步和鎖方面。

總覽

Java中的每個程序都受線程概念的約束。這意味着即使一個最簡單的“ Hello World”程序也可以編寫,只是執行線程。但是-一個線程。當我們編寫多個這樣的線程並使它們以同步方式工作時,它將成爲一個正常運行的多線程程序。不要被“線程”一詞所混淆,因爲除非你意識到多線程和多進程之間有區別,否則它可以被視爲Java中的進程。我們稍後再處理。但是現在,讓我們探討一個問題:多線程或多進程編程的意義是什麼?

多進程

進程通常是根據一組相當線性的指令執行的,偶爾會給出循環,gotos和跳轉的指令。最初,在簡單的硬件系統下可以。但是,隨着時間的推移,隨着晶體管數量的增加,處理器發生了巨大的發展。(晶體管是處理器從中獲得功率的最細微的部分。最初的想法是:越多越好,摩爾定律。)它們變得越來越緊湊和小巧,易於以更大的處理能力來處理更多的工作。它開始以毫秒爲單位完成其任務,而較早的原型則耗時數分鐘甚至數小時。因此,在系統忙於I/O工作時,處理器大部分時間處於休眠狀態。因此,爲了最大程度地利用處理器,程序員開始分批處理它,這意味着將許多任務一起編譯並交給處理器,以使處理器忙一會兒。但是,處理器大部分時間都處於睡眠狀態,因爲程序的很大一部分還處理I/O子系統。結果,一旦I/O工作開始,處理器就可以再次自由進入睡眠狀態。處理器與I/O處理幾乎沒有關係。

這導致了多進程的想法。在多進程中,將分叉一個進程以創建其自身的副本,以便該指令的一部分可以佔用其CPU時間,而另一部分則忙於其他工作,例如處理I/O子系統。這樣,多個進程可以同時執行,從而使CPU大部分時間保持繁忙。但是,此技術的主要問題是共享資源的同步使用。必須規定,競爭進程不會陷入競爭或僵局的情況,例如,進程P1在釋放R1之前在尋找另一個資源(R2)的同時持有資源R1。同時,另一個進程P2可能正在保留R2,並在釋放資源之前查找由P1保留的資源R1。簡而言之(爲了打亂CPU的睡眠,程序員開始了不眠之夜),這是一個複雜的場景,但是有一些方法可以通過信號量,互斥量等技術來克服它們。現在不要進入。

但是,多線程適用於何處?

好吧,多進程很好,但是多線程更好。就game而言,多線程和多進程意味着同一件事——使CPU忙於處理多組共享指令。兩種情況都有助於產生新的併發流程。毫無疑問,這兩者在具有多核CPU的機器上都是有效的,在機器上可以調度進程流在空閒處理器之間跳轉,從而通過並行和分佈式處理利用執行速度。但是,區別在於線程一詞。與進程不同,線程對資源的需求較少。它比派生或生成進程的開銷要少。線程不會像進程一樣分配新的系統虛擬內存空間和環境。線程共享相同的地址空間,並通過定義一個函數來產生它們,並且其參數由線程處理。線程不僅在多核系統上有效,而且可以通過利用I/O的延遲以及其他導致執行停止的系統功能來提高單個處理系統的性能。

但是,多進程或多線程本身都不是並行編程。它們很好地利用了非分佈式環境中的效率。並行編程是完全不同的模型。有專門用於分佈式計算環境的技術,例如MPIPVM。線程的遊樂場被限制在單個計算機系統內。

Java中的線程如何工作

Java中的線程是通過擴展Thread類或Runnable接口來實現的。run()方法在擴展類中被覆蓋。 請注意,JVM會從底層操作系統獲取創建和執行線程所需的所有幫助。實際上,線程的創建和執行是從JNI函數調用開始的,後者依次使用POSIX API(在Linux中)創建和執行線程。 java.lang中的Thread類定義如下:

package java.lang;
// ...
public class Thread implements Runnable {
   // ...
   public synchronized void start() {
      if (threadStatus != 0)
         throw new IllegalThreadStateException();
 
      group.add(this);
 
      Boolean started = false;
      try {
         start0();
         started = true;
      } finally {
         try {
            if (!started) {
               group.threadStartFailed(this);
            }
         } catch (Throwable ignore) {
         /* do nothing. If start0 threw a Throwable then
            it will be passed up the call stack */
         }
      }
   }
   // ...
   private native void start0();
   //  ...
   private native void setPriority0(int newPriority);
   private native void stop0(Object o);
   private native void suspend0();
   private native void resume0();
   private native void interrupt0();
   private native void setNativeName(String name);
   // ...
}


Java創建Thread對象時,對start()方法的調用將啓動一個新線程。請注意,start()方法調用了start0()本機方法。start0()是使用C/C++編寫的特定於平臺的本機方法。通過JNI調用此方法。JNI是本機方法接口的規範,描述了Java如何與用本機代碼編寫的程序進行交互以及如何將其集成到Java中。請參閱Java本機接口概述。

Linux JVM中,Java線程使用POSIX API調用作爲其本機實現。

因此,對POSIX線程API的研究可以揭示Java線程內部功能的更多複雜細節。

線程同步和鎖

顯然,當執行多個線程時,必須有某種機制可以在線程之間建立通信。在Java中,線程可以通過幾種方式相互通信。但是,在多線程通信期間發生的問題是同步,沒有同步,一個線程可能會不一致地修改共享數據,而另一線程正在更新共享數據。此結果是錯誤的,必須避免。

處理同步的基本方法之一是使用監視器。每個Java對象都與一個監視器關聯。監視器可以通過線程鎖定或解鎖,但前提條件是隻有一個線程可以在監視器上保持鎖。嘗試鎖定監視器的任何其他線程都將被阻止,直到釋放它爲止。

Java提供了一個關鍵字來進行同步語句。指定的同步語句將阻止所有嘗試訪問監視器鎖的嘗試,直到成功完成對象監視器的鎖操作爲止。一旦進行了鎖定,就只能執行同步的語句。程序員不必費心;成功或突然完成後,將在同一監視器上自動執行解鎖操作。因此,同步塊中的語句可確保一致性。

因此,Java不需要檢測任何死鎖條件,也不會阻止死鎖條件。 根據Java語言規範,...線程在多個對象上(直接或間接)持有鎖的程序應使用常規技術來避免死鎖,並在必要時創建不死鎖的高級鎖定原語。

// Ensures that only one thread can execute at a time the sync
// object is the reference whose lock associates with the monitor
// the code within this block ensures synchronized execution
 
synchronized (sync_obj) {
 
  // Process shared resources and variables
 
}


結論

從長遠角度來看,在Java中使用線程僅具有膚淺的理解可能會造成混淆和直覺。在這裏,我們試圖觸及多線程的兩個最重要方面,儘管尚不完整,但仍足以使研究朝着更好的理解方向發展。多線程還有其他複雜的方面,我們將在隨後的內容中提示要尋找的內容和內容。

歡迎留言或私信探討學習~

 


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