美國NASDAQ上市公司
外商IT教育培訓企業

0800-777-100


週一到週五 09:00~21:00 週六到週日 09:00~18:00

熱門課程

關於Java並發編程的總結和思考

  • 時間:2017-06-15
  • 發布:達內教育台灣
  • 來源:IT

為什麽需要並發

並發其實是一種解藕合的策略,它幫助我們把做什麽(目標)和什麽時候做(時機)分開。這樣做可以明顯改進應用程序的吞吐量(獲得更多的CPU調度時間)和結構(程序有多個部分在協同工作)。

做過Java Web 開發的人都知道,Java Web中的Servlet程序在Servlet容器的支持下采用單實例多線程的工作模式,Servlet容器為妳處理了並發問題。

誤解和正解

最常見的對並發編程的誤解有以下這些:

-並發總能改進性能(並發在CPU有很多空閑時間時能明顯改進程序的性能,但當線程數量較多的時候,線程間頻繁的調度切換反而會讓系統的性能下降)

-編寫並發程序無需修改原有的設計(目的與時機的解藕往往會對系統結構產生巨大的影響)

-在使用Web或EJB容器時不用關註並發問題(只有了解了容器在做什麽,才能更好的使用容器)

下面的這些說法才是對並發客觀的認識:

- 編寫並發程序會在代碼上增加額外的開銷

- 正確的並發是非常復雜的,即使對於很簡單的問題

- 並發中的缺陷因為不易重現也不容易被發現

- 並發往往需要對設計策略從根本上進行修改

並發編程的原則和技巧

單一職責原則

分離並發相關代碼和其他代碼(並發相關代碼有自己的開發、修改和調優生命周期)。

限制數據作用域

兩個線程修改共享對象的同一字段時可能會相互幹擾,導致不可預期的行為,解決方案之一是構造臨界區,但是必須限制臨界區的數量。

使用數據副本

數據副本是避免共享數據的好方法,復制出來的對象只是以只讀的方式對待。Java 5的java.util.concurrent包中增加一個名為CopyOnWriteArrayList的類,它是List接口的子類型,所以妳可以認為它是ArrayList的線程安全的版本,它使用了寫時復制的方式創建數據副本進行操作來避免對共享數據並發訪問而引發的問題。

線程應盡可能獨立

讓線程存在於自己的世界中,不與其他線程共享數據。有過Java Web開發經驗的人都知道,Servlet就是以單實例多線程的方式工作,和每個請求相關的數據都是通過Servlet子類的service方法(或者是doGet或doPost方法)的參數傳入的。

只要Servlet中的代碼只使用局部變量,Servlet就不會導致同步問題。springMVC的控制器也是這麽做的,從請求中獲得的對象都是以方法的參數傳入而不是作為類的成員,很明顯Struts 2的做法就正好相反,因此Struts 2中作為控制器的Action類都是每個請求對應一個實例。

Java 5以前的並發編程

Java的線程模型建立在搶占式線程調度的基礎上,也就是說:

-所有線程可以很容易的共享同一進程中的對象。

-能夠引用這些對象的任何線程都可以修改這些對象。

-為了保護數據,對象可以被鎖住。

Java基於線程和鎖的並發過於底層,而且使用鎖很多時候都是很萬惡的,因為它相當於讓所有的並發都變成了排隊等待。

在Java 5以前,可以用synchronized關鍵字來實現鎖的功能,它可以用在代碼塊和方法上,表示在執行整個代碼塊或方法之前線程必須取得合適的鎖。

對於類的非靜態方法(成員方法)而言,這意味這要取得對象實例的鎖,對於類的靜態方法(類方法)而言,要取得類的Class對象的鎖,對於同步代碼塊,程序員可以指定要取得的是那個對象的鎖。

不管是同步代碼塊還是同步方法,每次只有一個線程可以進入,如果其他線程試圖進入(不管是同一同步塊還是不同的同步塊),JVM會將它們掛起(放入到等鎖池中)。這種結構在並發理論中稱為臨界區(critical section)。

這裏我們可以對Java中用synchronized實現同步和鎖的功能做一個總結:

- 只能鎖定對象,不能鎖定基本數據類型

- 被鎖定的對象數組中的單個對象不會被鎖定

- 同步方法可以視為包含整個方法的synchronized(this) { … }代碼塊

- 靜態同步方法會鎖定它的Class對象

- 內部類的同步是獨立於外部類的

- synchronized修飾符並不是方法簽名的組成部分,所以不能出現在接口的方法聲明中

- 非同步的方法不關心鎖的狀態,它們在同步方法運行時仍然可以得以運行

- synchronized實現的鎖是可重入的鎖。

在JVM內部,為了提高效率,同時運行的每個線程都會有它正在處理的數據的緩存副本,當我們使用synchronzied進行同步的時候,真正被同步的是在不同線程中表示被鎖定對象的內存塊(副本數據會保持和主內存的同步,現在知道為什麽要用同步這個詞匯了吧)。

簡單的說就是在同步塊或同步方法執行完後,對被鎖定的對象做的任何修改要在釋放鎖之前寫回到主內存中;在進入同步塊得到鎖之後,被鎖定對象的數據是從主內存中讀出來的,持有鎖的線程的數據副本一定和主內存中的數據視圖是同步的。

在Java最初的版本中,就有一個叫volatile的關鍵字,它是一種簡單的同步的處理機制,因為被volatile修飾的變量遵循以下規則:

- 變量的值在使用之前總會從主內存中再讀取出來。

- 對變量值的修改總會在完成之後寫回到主內存中。

使用volatile關鍵字可以在多線程環境下預防編譯器不正確的優化假設(編譯器可能會將在一個線程中值不會發生改變的變量優化成常量),但只有修改時不依賴當前狀態(讀取時的值)的變量才應該聲明為volatile變量。

不變模式也是並發編程時可以考慮的一種設計。讓對象的狀態是不變的,如果希望修改對象的狀態,就會創建對象的副本並將改變寫入副本而不改變原來的對象,這樣就不會出現狀態不一致的情況,因此不變對象是線程安全的。

Java中我們使用頻率極高的String類就采用了這樣的設計。說到這裏妳可能也體會到final關鍵字的重要意義了。

感謝大家的閱讀關註 更多精彩內容請關註Java培訓官網

上一篇:2017年高考填報志願選擇IT行業的五大理由
下一篇:零基礎的人如何入門學習IOS開發?
選擇城市和中心
貴州省

廣西省

海南省