天天看點

銀行取款[多線程]{使用volatile修飾共享變量,但此場景并不保證線程同步}

經典例子:老婆(朱麗葉)老公(羅密歐),使用銀行卡和存折,或者網銀等,同時對同一賬戶操作的安全問題。

此處用多線程實作,同時取款的模拟實作,使用volatile修飾共享變量,但此場景并不保證線程同步,檢視取款安全隐患問題,代碼如下:

我學習位址(thanks for auther):

<a target="_blank" href="http://www.ibm.com/developerworks/cn/java/j-jtp06197.html">java 理論與實踐: 正确使用 volatile 變量</a>

<a target="_blank" href="http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html">java中volatile關鍵字的含義</a>

--------------------------------------------------------------------------------------------------------------------------------------

java語言内在兩種同步機制:同步塊(或方法)和 volatile 變量。都是為了實作代碼線程的安全性。

在jvm運作記憶體中jvm棧中,對每個線程都配置設定一個線程棧(儲存線程變量資訊),當線程運作通路某個對象的值時,首先通過對象引用找到對應堆記憶體的變量的值,

然後把堆記憶體變量的具體值加載(load)到線程本地記憶體才能中,建立一個變量的副本。之後,線程就不再和對象在堆記憶體變量的值有關系,而直接修改副本變量的值,在修改修改副本變量的值0完的某一時刻(線程退出之前),

自動把線程變量副本的值回寫到對象在堆中的變量中。這樣堆中的對象的值就發生變化了。

普通變量:從主存讀取(read)加載(load)變量值到目前工作記憶體,(執行代碼,改變變量的值使用指派寫記憶體[use-asign-store])(可多次),最終工作記憶體資料(write)記憶體變量值,此并非原子性,如果主存的值發生修改,線程工作記憶體的值已經加載,不會産生改變,是以預期結果不一樣。

volatile變量:jvm保證主記憶體加載到線程工作記憶體中的值時最新的。

線程1和線程2開始運作,讀取加載volatile變量[如banlance],發現主存為banlance=500,則都加載最新這個值,

線程1修改banlance的值write到主存中,主存中的變為banlance=400,則線程2,由于已經進行read和load在進行運算後,也會更新主存banlance=400,即使用volatile修飾,還是會出現并發的問題。

用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的最的值。volatile很容易被誤用,用來進行原子性操作。

鎖兩種主要特性:互斥(mutual exclusion)和可見性(visibility)。

互斥即一次隻允許一個線程持有某個特定的鎖,是以可使用該特性實作對共享資料的協調通路協定,一次就隻有一個線程能夠使用該共享資料。

可見即它必須確定前線程釋放鎖之前對共享資料做出的更改對于随後獲得該鎖的另一個線程是可見的,如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一緻的值,這将引發許多嚴重問題。

volatile 變量具有synchronized的可見性特性,但是不具備原子特性。

volatile 變量,出于簡易性或可伸縮性的考慮,您可能傾向于使用 volatile 變量而不是鎖。

volatile 變量不會像鎖那樣造成線程阻塞,是以也很少造成可伸縮性問題。

在某些情況下,如果讀操作遠遠大于寫操作,volatile 變量還可以提供優于鎖的性能優勢。

volatile關鍵字為域變量的通路提供了一種免鎖機制,使用volatile修飾域相當于告訴虛拟機該域可能會被其他線程更新,是以每次使用該域就要重新計算,而不是使用寄存器中的值,volatile不會提供任何原子操作,它也不能用

銀行賬戶:volatile int banlance

線程方法:

測試代碼:

測試結果:

分析結果,可見:

雙線程總共取款10次,賬戶總額為500.

取款結果:取款成功600元,餘額顯示不對,取款流程不對。

現實中,此取款代碼是有嚴重bug的,上邊資料對于銀行是不安全的,個人也會帶來不必要的麻煩。