南京達內IT培訓
美國上市IT培訓機構

025-66630866

熱門課程

判斷 Java 線程并發的安全性

  • 時間:2016-06-16 17:58
  • 發布:南京達內培訓學校
  • 來源:企業面試題

如何判斷 Java 線程并發的安全性?對于已經學習java到了一定程度的同學有沒有碰到過這樣的問題呢?當時碰到的時候有沒有成功解決呢?用的方法是什么呢?下面讓南京java培訓機構南京達內培訓老師帶領小白們認識一下:

在高并發的時代中,如何寫出高質量的并發程序一直是一個令人頭疼的事情?,F在給你一段代碼,你如何判斷它是否是線程安全的?又如何改進呢?在這里,我們簡單介紹一下Java內部是如果保證線程安全的。一切的關鍵就在:

高效利用并發,同時也必須保證JMM三大特性的有序性。只有保證了有序性,才能在代碼正確執行的前提下去求追更高的效率。而這個王牌就是:先行發生原則

如果Java中所有的同步操作都交給synchronized或者volatile來做,那將是很麻煩的??紤]你要寫一個并發環境下的程序,你得花費大半的精力來實現并發的線程安全性。而這對于新手來說,幾乎是不可能完成的任務。所以Java定義了一些規則,使得Java會自動判斷數據是否存在競爭,線程是否安全等。通過這個先行并發原則,Java可以一攬子解決并發環境下兩個操作之間是否可能存在沖突的所有問題。那么,什么是先行發生呢?

一、先行發生原則

離散數學中曾經定義了“偏序”的概念,在Java中正是使用了這個概念。偏序通俗的來理解就是拓撲結構,一個DAG。如果操作A先行發生于B,那么B將能觀察到A所做的所有事情(包括修改變量、發送消息、調用方法等)。這個可以用例子來說明:
1 //線程A 2 i = 1; 3 4 //線程B 5 j = i; 6 7 //線程C 8 i = 2;如果只考慮A和B,并且保證A先行發生于B,那么B的值是多少?答案很顯然是1。依據有兩個:   1.根據先行發生的偏序關系,i的值的改變可以被j觀察到   2.在線程C修改i值之前,線程A結束之后沒有其他線程會修改i。   但是如果考慮C線程,很悲桑,j的值會不確定。因為線程B和線程C沒有先行發生定義的偏序,意思就是C可以發生在B前,B后的任意位置,如果C發生在A/B之間,那么顯然j的值就是2了。所以線程B就不具備多線程安全性。為了解決這種問題,JMM定義了一些”天然的“先行發生關系來保證多線程的安全。假如所有的操作都像A/B定義好了偏序關系,那么并發就不會有任何難度了。但是為了更靈活的使用,Java只對一些場景定了先行發生原則,所以遇到這幾個規則范圍內的問題,我們就不需要考慮并發的各種問題,Java會自動幫我們解決。所以,以下操作無須任何同步手段就能保證并發的成功:   1、程序次序規則:一個線程內,代碼的執行會按照程序書寫的順序   2、管程鎖定原則:對同一變量的unlock操作先行發生于后來的lock操作   3、volatile變量規則:對一個volatile的寫操作先行發生于后來的讀操作   4、線程啟動原則:Thread的start()先行發生于線程內的所有動作   5、線程終止原則:線程內的所有動作都先行發生于線程的終止檢測   6、線程中斷原則:對線程調用interrupt()先行發生于被中斷的代碼檢測到是否有中斷發生  7、對象終結原則:一個對象的初始化操作先行發生于finalize()方法  8、傳遞性:A先行發生于B,B先行發生于C,那么A先行發生于C二、先行發生原則的應用上面我們介紹了先行發生原則,下面我們就用實例來看看,它是如何工作的??紤]這樣一段代碼:
1 private int value = 0; 2 3 public int getValue() { 4 return this.value; 5 } 6 7 public void setValue(int value) { 8 this.value = value; 9 }

上面是一段再普通不過的getter/setter方法了,現在假設有A/B兩個線程。線程A先(時間上的先后)調用了setValue(2),然后線程B調用了同一個對象的getValue(),那么線程B收到的返回值是多少呢?

那么,我們就用前面介紹的先行發生原則來判斷一下:

1.因為A/B不是一個線程,所以無法使用程序次序原則
2.因為沒有synchronized,所以不存在lock/unlock操作,無法使用管程鎖定原則
3.沒有volatile修飾,不能使用volatile變量原則
4.因為是兩個獨立的線程,所以線程啟動、終止、終端原則都不能使用
5.因為不涉及對象的初始化和finalize(),所以無法使用對象終結原則
6.因為根本就沒有一個先行發生原則,所以也不能使用傳遞性

綜上所述,我們發生A/B之間不滿足所有的先行發生原則。所以A/B線程的操作不是線程安全的。如果想要線程安全,必須通過程序員自己去實現。這里提供2個方法:

1.將getter/setter方法添加synchronized修飾,使之滿足管程鎖定原則
2.把value定義為volatile,因為setter中對value的修改不依賴value的原值,所以符合volatile的使用場景(一定要符合前提,如果這里方法是value++就肯定不行了),然后套用volatile變量原則就可以保證了

通過上面的例子,我們可以得出一個結論:

時間上先發生的操作是無法保證“先行發生”的。那如果一個操作滿足”先行發生“的定義,是否就一定是時間上的先發生呢?很遺憾,這個結論也是不成立的。一個典型的例子就是提到過的指令重排序:
1 //以下操作在同一個線程內執行 2 int i = 1; 3 int j = 2;既然是同一個線程,那么肯定滿足程序次序原則。所以int i = 1;先行發生于int j = 2;,但是int j = 2;完全可能被處理器先處理,這并不影響先行發生原則的正確性(我猜測是JVM在滿足先行發生原則的基礎上,會對某些無關語句進行指令重排序優化),從而我們無法在線程中感知。Java有句話是這樣說的就是這個道理: 線程內觀察指令全部是串行的,而在其他線程觀察那個線程,會發現內部程序的執行是雜亂無章的。但JVM會保證結果的正確性   時間上的先后順序與先行發生原則之間沒有太大的關系,所以我們衡量并發安全問題的時候不能受到時間順序的干擾,一切必須以先行發生原則為準。

上一篇:Java學習中類的實例化的方法
下一篇:java中使用Map中常見問題解答
選擇城市和中心
江西省

貴州省

廣西省

海南省

网址在线观看你懂我意思吧