[英]Java concurrent visibility of primitive array writes
我最近在我的代碼庫中找到了這個gem:
/** This class is used to "publish" changes to a non-volatile variable.
*
* Access to non-volatile and volatile variables cannot be reordered,
* so if you make changes to a non-volatile variable before calling publish,
* they are guaranteed to be visible to a thread which calls syncChanges
*
*/
private static class Publisher {
//This variable may not look like it's doing anything, but it really is.
//See the documentaion for this class.
private volatile AtomicInteger sync = new AtomicInteger(0);
void publish() {
sync.incrementAndGet();
}
/**
*
* @return the return value of this function has no meaning.
* You should not make *any* assumptions about it.
*/
int syncChanges() {
return sync.get();
}
}
這樣使用:
線程1
float[][] matrix;
matrix[x][y] = n;
publisher.publish();
線程2
publisher.syncChanges();
myVar = matrix[x][y];
線程1是一個連續運行的后台更新線程。 線程2是一個HTTP工作線程,它不關心它讀取的內容是以任何方式一致的還是原子的,只是寫入“最終”到達並且不會作為並發神的提供而丟失。
現在,這會觸發我所有的警告鈴聲。 自定義並發算法深入寫入無關代碼。
不幸的是,修復代碼並非易事。 Java對並發原始矩陣的支持並不好。 看起來最明確的解決方法是使用ReadWriteLock
,但這可能會對性能產生負面影響。 顯而易見,正確性更為重要,但似乎我應該在將其從性能敏感區域中剝離之前證明這是不正確的。
根據java.util.concurrent文檔 ,以下創建happens-before
關系:
線程中的每個動作都發生在該線程中的每個動作之前,該動作在程序的順序中稍后出現。
在每次后續讀取同一字段之前,會發生對易失性字段的寫入。 易失性字段的寫入和讀取具有與進入和退出監視器類似的內存一致性效果,但不需要互斥鎖定。
所以聽起來像:
所以代碼確實已經建立了矩陣的先發條件鏈。
但我不相信。 並發很難,我不是域專家。 我錯過了什么? 這確實安全嗎?
在可見性方面,您需要的是在任何易失性字段上的易讀寫入。 這會奏效
final float[][] matrix = ...;
volatile float[][] matrixV = matrix;
線程1
matrix[x][y] = n;
matrixV = matrix; // volatile write
線程2
float[][] m = matrixV; // volatile read
myVar = m[x][y];
or simply
myVar = matrixV[x][y];
但這只適用於更新一個變量。 如果編寫器線程正在寫入多個變量並且讀取線程正在讀取它們,則讀者可能會看到不一致的圖片。 通常它由讀寫鎖處理。 寫時復制可能適用於某些使用模式。
Doug Lea有一個新的“StampedLock” http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166edocs/jsr166e/StampedLock.html for Java8,它是一個讀寫鎖的版本,讀取便宜得多鎖。 但它也很難使用。 基本上讀者獲取當前版本,然后繼續讀取一堆變量,然后再次檢查版本; 如果版本沒有更改,則在讀取會話期間沒有並發寫入。
這對於向矩陣發布單個更新看起來確實安全,但當然它不提供任何原子性。 這是否可行取決於您的應用程序,但它應該記錄在這樣的實用程序類中。
但是,它包含一些冗余,可以通過使sync
字段final
來改進。 該字段的volatile
訪問是兩個內存屏障中的第一個; 通過契約,調用incrementAndGet()
對內存的影響與寫入和讀取volatile變量相同,並且調用get()
與讀取具有相同的效果。
因此,代碼可以單獨依賴這些方法提供的同步,並使字段本身final
。
使用volatile
不是同步所有內容的靈丹妙葯。 保證如果另一個線程讀取volatile變量的更新值,它們也會看到在此之前對非volatile變量進行的每個更改。 但是沒有什么能保證其他線程會讀取更新的值 。
在示例代碼中,如果您對matrix
進行多次寫入然后調用publish()
,而另一個線程調用synch()
然后讀取矩陣,則另一個線程可能會看到一些,全部或沒有任何更改:
看到這篇文章
你正確地提到了之前發生過關系的規則#2
在每次后續讀取同一字段之前,會發生對易失性字段的寫入。
但是,它不保證在絕對時間軸上的syncChanges()之前調用publish()。 讓我們稍微改變你的例子。
線程1:
matrix[0][0] = 42.0f;
Thread.sleep(1000*1000); // assume the thread was preempted here
publisher.publish(); //assume initial state of sync is 0
線程2:
int a = publisher.syncChanges();
float b = matrix[0][0];
a和b變量的選項有哪些?
怎么處理呢? 這取決於業務邏輯。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.