2017-04-22 39 views
4

讓我們考慮使用Java下列標準同步:Java - 鎖定保證如何在關係之前發生?

public class Job { 
    private Lock lock = new ReentrantLock(); 

    public void work() { 
     lock.lock(); 
     try { 
      doLotsOfWork(); 
     } finally { 
      lock.unlock(); 
     } 
    } 
} 

我理解的基礎上的Javadoc,這相當於​​塊。我很努力地看到這是如何在更低層次上實施的。

Lock有一個易失性狀態,一旦調用lock()它執行一個易失性讀取,然後在釋放後執行易失性寫入。一個對象的狀態寫如何確保,即沒有指令的doLotsOfWork,這可能會觸及許多不同的對象,不會被亂序執行?

或者想象doLotsOfWork實際上是與1000+行代碼取代。很明顯,編譯器無法預先知道鎖內存在易失性,因此需要停止重新排序指令。那麼,如何保證lock/unlock,即使它是圍繞單獨對象的易失性狀態構建的?

+0

我沒有看到Lock/ReentrantLock實現中的任何volatile。您可以在以下網址查看ReentraltLock實施:http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/locks/ReentrantLock.java/?v=來源 –

+0

通過其實施。它沒有被定義。只需要。 – EJP

+0

AbstractQueuedSYnchronizer的狀態是一個易變的變量 – Bober02

回答

2

好吧,如果我理解正確,則你的答案是here。易讀寫引入內存屏障LoadLoad,​​等等禁止重新排序。在CPU級別,這會轉換爲實際的內存障礙,如mfencelfence(CPU也會通過其他一些機制強制執行非重新排序,因此您可能會在機器代碼中看到其他內容)。

這裏是一個小例子:

i = 42; 
j = 53; 
[StoreStore] 
[LoadStore] 
x = 1; // volatile store 

ij分配可以被重新排序之間的話,但他們無法x=1或者換句話說i and j不能低於X。

同樣適用於volatile reads

對於您的示例,doLotsOfWork中的每個操作都可以根據編譯器的需要重新排序,但不能通過lock operations重新排序。

此外,當你說編譯器不知道有一個volatile read/write,你有點錯了。它必須知道,,否則將沒有其他方式來阻止這些重新排序。

另外,最後一點:由於jdk-8,您可以通過Unsafe強制執行非重新排序,除了易失性外,還可以提供其他方式。

1

從Oracle的documentation:的 同場以後每讀

volatile寫之前發生。寫和volatile領域的讀取也有類似的 內存一致性效果進入和退出顯示器,但做 意味着互斥鎖。

Java併發實踐指出它更清楚:

volatile變量的可視性效果超出volatile變量本身的價值 。當線程A寫入到volatile 變量,並且隨後線程B讀取相同的變量,所有變量寫入 volatile變量變得可見之前是可見的,以的 值乙讀取volatile 變量之後。

適用於ReentrantLock這意味着一切都執行之前lock.unlock()doLotsOfWork()你的情況),將保證之前lock.lock()後續調用發生。 doLotsOfWork()內的指令仍然可以在它們之間重新排序。即保證這裏的唯一的事情是,這將隨後獲得該鎖調用lock.lock()會看到調用lock.unlock()doLotsOfWork()所做的一切變化的任何線索。

+0

我明白了。這是JMM的高級描述。我想知道如何寫入一個volatile變量意味着沒有重新排序可能會導致錯誤,這是編譯器事先不知道的(也就是說有一個volatile變量的狀態變量) – Bober02

相關問題