使对易失性字段的操作原子化
總覽
易失字段的預期行為是,它們在多線程應用程序中的行為應與在單線程應用程序中的行為相同。 禁止它們表現相同的方式,但不能保證它們表現相同的方式。
Java 5.0+中的解決方案是使用AtomicXxxx類,但是這些類在內存(它們添加標頭和填充),性能(它們添加引用和對其相對位置的控制很少)方面效率相對較低,并且在語法上不是易于使用。
恕我直言,一個簡單的解決方案,如果可變字段能夠像預期的那樣起作用,那么JVM必須在AtomicFields中支持的方式是當前JMM(Java內存模型)中所禁止的,但不能保證。
為什么要使字段
volatile字段的好處是,它們在線程中可見,并且某些避免重新讀取它們的優化已被禁用,因此即使您沒有更改它們,也總是要再次檢查當前值。
例如不揮發
Thread 2: int a = 5;Thread 1: a = 6;(后來)
Thread 2: System.out.println(a); // prints 5 or 6具有揮發性
Thread 2: volatile int a = 5;Thread 1: a = 6;(后來)
Thread 2: System.out.println(a); // prints 6 given enough time.為什么不一直使用
易失的讀寫訪問速度要慢得多。 當您寫入易失性字段時,它會使整個CPU管道停頓,以確保已將數據寫入緩存。 否則,即使在同一線程中,也有可能在下一次讀取該值時看到一個舊值(請參閱AtomicLong.lazySet(),這樣可以避免流水線停頓)
懲罰可能會慢10倍左右,您不想在每次訪問時都這樣做。
一個重要的限制是,即使您可能認為對字段的操作也不是原子的。 甚至比通常情況更糟,沒有區別。 也就是說,它似乎可以工作很長時間甚至數年,并且由于偶然的更改(例如所使用的Java版本),甚至對象加載到內存中而突然/隨機中斷。 例如,在運行程序之前加載了哪些程序。
例如更新值
Thread 2: volatile int a = 5;Thread 1: a += 1; Thread 2: a += 2;(后來)
Thread 2: System.out.println(a); // prints 6, 7 or 8 even given enough time.這是一個問題,因為對a的讀取和對a的寫入是分別完成的,并且您可以獲得競爭條件。 99%以上的時間它會表現出預期,但有時卻不會。
你能為這個做什么?
您需要使用AtomicXxxx類。 這些將易失性字段包裝為具有預期行為的操作。
Thread 2: AtomicInteger a = new AtomicInteger(5);Thread 1: a.incrementAndGet(); Thread 2: a.addAndGet(2);(后來)
Thread 2: System.out.println(a); // prints 8 given enough time.我有什么建議?
JVM具有一種按預期方式運行的方法,唯一令人驚訝的事情是您需要使用特殊的類來執行JMM不能保證的工作。 我建議更改JMM以支持并發AtomicClasses當前提供的行為。
在每種情況下,單線程行為都是不變的。 沒有看到競爭條件的多線程程序將表現相同。 區別在于,多線程程序不必查看競爭條件,而可以更改基本行為。
| x.getAndIncrement() | x ++或x + = 1 | |
| x.incrementAndGet() | ++ x | |
| x.getAndDecrment() | x–或x-= 1 | |
| x.decrementAndGet() | -X | |
| x.addAndGet(y) | (x + = y) | |
| x.getAndAdd(y) | ((x + = y)-y) | |
| x.compareAndSet(e,y) | (x == e?x = y,true:false) | 需要添加逗號語法 在其他語言中使用。 |
所有基本類型(例如布爾,字節,short,int,long,float和double)都可以支持這些操作。
可以支持其他賦值運算符,例如:
| 原子乘法 | x * = 2; | |
| 原子減法 | x-= y; | |
| 原子分裂 | x / = y; | |
| 原子模量 | x%= y; | |
| 原子位移 | x << = y; | |
| 原子位移 | x >> = z; | |
| 原子位移 | x >>> = w; | |
| 原子和 | x&=?y; | 清除位 |
| 原子或 | x | = z; | 設置位 |
| 原子異或 | x ^ = w; | 翻轉位 |
有什么風險?
這可能會破壞依賴于這些操作的代碼,這些代碼有時會由于競爭條件而失敗。
可能無法以線程安全的方式支持更復雜的表達式。 這可能會導致令人驚訝的錯誤,因為代碼看起來像是正常的,但事實并非如此。 永遠不會比當前狀態更糟。
JEP 193 –增強揮發性
有一個JEP 193將該功能添加到Java。 一個例子是:
class Usage {volatile int count;int incrementCount() {return count.volatile.incrementAndGet();} }恕我直言,這種方法有一些限制。
- 語法是相當重要的變化。 更改JMM可能不需要更改Java語法,也可能不需要更改編譯器。
- 這是一種不太通用的解決方案。 支持體積+ =數量等操作可能很有用; 這些是雙重類型。
- 開發人員要了解為什么他/她應該使用此代碼而不是x ++ ,這會給開發人員帶來更多負擔;
我不相信使用更麻煩的語法可以更清楚地了解正在發生的事情。 考慮以下示例:
volatile int a, b;a += b;要么
a.volatile.addAndGet(b.volatile);要么
AtomicInteger a, b;a.addAndGet(b.get());作為行,這些操作中的哪個是原子的。 他們都不回答,但是使用Intel TSX的系統可以使這些原子化,如果您要更改這些代碼行中任何一個的行為,我都可以使a + = b; 而不是發明一種新的語法,該語法在大多數情況下會做同樣的事情,但是可以保證一種語法,而不能保證另一種。
結論
如果JMM保證等效的單線程操作的行為符合多線程代碼的預期,則可以消除使用AtomicInteger和AtomicLong的語法和性能開銷。
可以使用字節碼檢測將該功能添加到Java的早期版本中。
翻譯自: https://www.javacodegeeks.com/2014/07/making-operations-on-volatile-fields-atomic.html
總結
以上是生活随笔為你收集整理的使对易失性字段的操作原子化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机手写笔(手机手写笔画粗细怎么设置)
- 下一篇: 电脑k歌麦克风推荐(推荐一款k歌麦克风)