JAVA併發一:Java 併發編程簡介

推薦閱讀:阿里架構師直言:“沒有實戰都是紙上談兵”!Redis實戰PDF分享

                  奮發圖強半年多,終於四面阿里如願拿到心儀offer定級P7

                  阿里P8架構師談:工作1-5年的Java工程師,怎樣提高核心競爭力

簡介:

​        操作系統的出現使得計算機每次能運行多個程序,並且不同的程序都在單獨的進程中運行:操作系統爲各個獨立執行的進程分配好資源,包括內存,文件句柄以及安全證書等,在不同的進程之間可以通過一些粗粒度的通信機制來交換數據,包括:套接字、信號處理器、共享內存、信號量以及文件等;一種高效的運行方式是通過粗粒度的時間分片(Time Slicing)使這些用戶和程序能共享計算機資源,而不是由一個程序從頭到尾運行,在計算多個任務時,每個程序執行一個任務並在必要時相互通信。

​        線程被稱爲輕量級進程,在大多數現代操作系統中,以線程爲基本的調度單位,而不是進程,若沒有明確的協同機制,那麼線程將被彼此獨立執行。線程會共享進程範圍內的資源,例如內存句柄和文件句柄,但每個線程都有各自的程序計數器、棧以及局部變量等。由於同一個進程中的所有線程都將共享進程內存地址空間,因此這些線程都能訪問系統的變量並在同一個堆上分配對象,這就需要實現一種比在進程間共享數據力度更細的數據共享機制,如果沒有明確的同步機制來協同對共享數據的訪問,那麼當一個線程正在使用某個變量時,另一個線程可能同時訪問這個變量,將造成不可預測的後果。

優勢:

        服務器應用程序在接受來自多個遠程客戶端的套接字連接請求時,若爲每個連接都分配其各自的線程並且使用同步I/O,會降低此類程序的開發難度。

​        若某個應用程序對套接字執行讀操作而此時還沒有數據到來,那麼將一直阻塞,直到有數據到來。在單線程應用程序中,不僅意味着在處理請求的過程中將停頓,而且還意味着在這個線程被阻塞期間,對所有請求的處理都將停頓,爲避免此,單線程服務器應用程序必須使用非阻塞I/O,而其複雜性要遠遠高於同步I/O,並且容易出錯。然而,若每個請求都有自己的處理線程,那麼在處理某個請求時發生的阻塞將不會影響其他請求的處理。

風險:

​ 1、安全性問題:

        由於多個線程要共享相同的內存地址空間,並且是併發運行,因此它們可能會訪問或修改其他線程正在使用的變量,導致結果是沒有同步情況下,線程會由於無法預料的數據變化而發生錯誤。

​         如果沒有同步,無論是編譯器、硬件還是運行時,都可以隨意安排操作的執行時間和順序,例如對寄存器或者處理器中的變量進行緩存,而這些被緩存的變量對於其他線程來說是暫時(永久)不可見的。

@NotThreadSafe
public class UnsafeSequence{
  private int value;
    public int getNext(){
        return value++;
    }
}

        事實上 value++ 包含三個獨立的操作:讀取 value,value+1,將計算結果寫入value;

        由於運行時多個線程之間的操作交替執行,因此可能同時執行讀操作,從而得到相同值並將其加1,結果是,在不同線程的調用中返回相同的數值。

2、活躍性問題:

​         活躍性意味着某件正確的事情最終會發生,在串行程序中,即無限循環,從而使循環之後的代碼無法得到執行,例如線程A在等待線程B釋放其持有的資源,而線程B永遠都不釋放該資源,那麼A就會永久地等待。

​ 3、性能問題:

​         場景:在多線程程序中,當線程調度器臨時掛起活躍線程並轉而運行另一個線程時,會頻繁地出現上下文切換操作,這種操作將帶來極大的開銷:保存和恢復執行上下文,丟失局部性,並且CPU時間將更多地花在線程調度而不是線程運行上;當線程共享數據時,必須使用同步機制,而這些機制往往會抑制某些編譯器優化,使內存緩存區中的數據無效,以及增加共享內存總線的同步流量,都將帶來額外性能開銷。

 

場景解析:

         當 JVM 啓動時,將爲 JVM 的內部任務(例,垃圾回收,終結操作等)創建後臺線程,並創建一個主線程來運行 main 方法。

         AWT 和 Swing:AWT 和 Swing 的用戶界面框架創建線程來管理用戶界面事件。當用戶出發某個 UI 動作時,在事件線程中就會有一個事件處理器被調用以執行用戶請求的操作,如果事件處理器需要訪問由其他線程同時訪問的應用程序狀態(例如編輯某個文檔),那麼該事件處理器,以及訪問這個狀態的所有其他代碼都必須採用一種線程安全方式來訪問該狀態。

         Timer:Timer 將創建線程來執行延遲任務。TimerTask 將在 Timer 管理的線程中執行,若 Task 訪問了其他線程訪問的數據,那麼 Task 需要以線程安全的方式來訪問數據,其他類也必須採用線程安全的方式來訪問該數據,實現該目標的最簡單方式是將線程安全封裝在共享對象內部確保 Task 訪問的對象本身是線程安全。

        Servlet 和 JavaServer Page:到達服務器的請求會通過一個過濾器鏈被分發到正確的Servlet或 JSP,當多個客戶端同時請求同一個Servlet服務時或者Servlet被多個線程同時調用,則必須確保線程安全;即使確保只有一個線程調用某個Servlet,該Servlet可能會訪問與其他多個Servlet共享的信息,例如保存在ServletContext中或者會話HttpSession中的對象,也必須正確協同對這些對象的訪問實現線程安全。

        遠程方法調用(RMI,RMI能夠調用在其他 JVM 中運行的對象):當調用遠程對象時,將在一個由 RMI 管理的線程中調用該對象,同一個遠程對象上的同一個遠程方法會在多個 RMI 線程中被同時調用,因此必須正確協同在多個對象中共享的狀態,以及對遠程對象本身狀態的訪問(由於同一個對象可能會在多個線程中被同時訪問),必須確保其自身的線程安全性。

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