并发编程的三大特性——原子性,可见性,有序性
一.原子性
??
??
火車售票系統例子:
??當客戶端A檢測到還有票時,執行賣票操作,還沒有執行更新票數的操作時客戶端B檢查了票數,發現大于0,于是又進行了一次賣票操作。這就出現了同一張票被賣了兩次的情況。
?
?
什么是原子性?
??可以將一段代碼想象成一個房間,每個線程就是要進入這個房間的人。如果A進入到這個房間后,還沒有出來,B也是可以進入此房間的,打斷A在房間里的隱私。此時就是不具備原子性的。
??怎么去解決這個問題呢?此時如果給房間加上一把鎖,A拿到鑰匙進去之后就把門鎖上,那么其他人就進不來了。這樣就保證了這段代碼的原子性。有時將這種現象叫做同步互斥。
?
?
參考代碼1:
運行結果:
??上述代碼申請了20個線程,在每個線程中都對num進行循環++操作,我們希望最終輸出num的結果是200000,但是實際debug運行之后每次結果都小于200000且每次結果都不一樣。這就是線程不安全以及沒有保證原子性造成的。實際上一個線程在進行num++操作時別的線程也可以對num進行操作。所以這時解決辦法就是利用原子性給類對象加上鎖,在得到鎖鑰匙的線程對內部代碼進行操作時,別的沒有得到鎖的線程是操作不了這段代碼的,接下來加上鎖來看看。
??
??
參考代碼2:
運行結果:
??
??加上鎖之后就可以解決這個問題了,獲得鎖的線程繼續執行代碼,而沒有競爭到鎖的線程處于阻塞態,做到了同步互斥。
??
??
各個線程之間競爭鎖的過程:
??
??
??
??
??
二.可見性
??
主內存——工作內存
??
Java內存模型:
??為了提高效率,JVM在執行過程中,會盡可能的將數據在工作內存中執行,但這樣會造成一個問題,共享變量在多線程之間不能及時看到改變,這個就是可見性問題。
??Java內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方式來實現可見性的,在這里,無論是普通變量還是volatile變量都是如此。兩者之間的區別就是:volatile的特殊規則保證了新值可以立即同步到主內存,以及每次使用前立即從主內存刷新。
??
volatile變量只能保證可見性,在不符合以下規則是還是需要通過加鎖來保證原子性。
??1.運算結果并不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。
??2.變量不需要與其他的狀態變量共同參與不變約束。
??
??
??
??
??
??
三.有序性
??
有這樣的情況:
比如你的老板分配給你3個任務:
1.去前臺拿文件
2.去買杯咖啡
3.去前臺取下u盤
??老板分配給你任務之后,其實自己是可以對這3個任務進行優化重排,可以去買咖啡之后再去前臺拿文件和u盤,這樣就會節省很多時間,效率也會大大提升。同樣CPU,JVM也是會對指令代碼進行優化重排的,這種叫做指令重排序。當然上述指令正確執行的前提是在單線程情況下。如果是在多線程場景下就有問題了,試想,如果在你去買咖啡的時候,文件或者u盤被另一個人拿走了,那此時就會出錯了。
??Java語言也是提供了volatile和synchronized兩個關鍵字來保證線程之間的操作的有序性。volatile關鍵字本身就包含了禁止指令重排序的語義,synchronized則是通過“一個變量在同一時刻只允許一條線程對其進行lock操作”這條規則來實現的。
??
??
??
總結:
?這就是并發編程的3種特性,而synchronized關鍵字在需要這3種特性時都可以作為一種解決方案,看起來很“萬能”,但是越這樣“萬能”的并發控制,也是會伴隨著一些性能的影響的(如線程越多,性能下降也越快,因為歸還對象鎖時,也是會伴隨著大量的線程喚醒,阻塞狀態的切換等)。
總結
以上是生活随笔為你收集整理的并发编程的三大特性——原子性,可见性,有序性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TW6869 drivers porti
- 下一篇: 父爱的表达方式