天天看點

java存儲變量共用_Java 并發程式設計(三):如何保證共享變量的可見性?

java存儲變量共用_Java 并發程式設計(三):如何保證共享變量的可見性?

摘要:

}        });        thread.start();        try {&nb

上一篇,我們談了談如何通過同步來保證共享變量的原子性(一個操作或者多個操作要麼全部執行并且執行的過程不會被任何因素打斷,要麼就都不執行),本篇我們來談一談如何保證共享變量的可見性(多個線程通路同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值)。

我們使用同步的目的不僅是,不希望某個線程在使用對象狀态時,另外一個線程在修改狀态,這樣容易造成混亂;我們還希望某個線程修改了對象狀态後,其他線程能夠看到修改後的狀态——這就涉及到了一個新的名詞:記憶體(可省略)可見性。

ength>=f.length?f.apply(null,args):currify.bind(null,...args)}}下面測試一下:functionadd(a,b){returna+b;

要了解可見性,我們得先來了解一下 Java 記憶體模型。

add);currified(1)(2);//3并且以上實作不隻是簡單的Currying化,可以是任意數量和任意次數的parialapplication:functionadd(a,b,c,d){re

Java 記憶體模型(Java Memory Model,簡稱 JMM)描述了 Java 程式中各種變量(線程之間的共享變量)的通路規則,以及在 JVM 中将變量存儲到記憶體→從記憶體中讀取變量的底層細節。

量)的通路規則,以及在JVM中将變量存儲到記憶體→從記憶體中讀取變量的底層細節。要知道,所有的變量都是存儲在主記憶體中的,每個線程會有自己獨立的工作記憶體,裡面儲存了該線程使用到的變量副本(主記憶體中

要知道,所有的變量都是存儲在主記憶體中的,每個線程會有自己獨立的工作記憶體,裡面儲存了該線程使用到的變量副本(主記憶體中變量的一個拷貝)。見下圖。

;     chenmo = true;    }}這段代碼的本意是:在主線程中建立子線程

java存儲變量共用_Java 并發程式設計(三):如何保證共享變量的可見性?

同步的目的不僅是,不希望某個線程在使用對象狀态時,另外一個線程在修改狀态,這樣容易造成混亂;我們還希望某個線程修改了對象狀态後,其他線程能夠看到修改後的狀态——這就涉及到了一個

ctioncurrify(){constargs=Array.prototype.slice.call(arguments);returnargs.length>=f.length?f.appl

同步的目的不僅是,不希望某個線程在使用對象狀态時,另外一個線程在修改狀态,這樣容易造成混亂;我們還希望某個線程修改了對象狀态後,其他線程能夠看到修改後的狀态——這就涉及到了一個

ctioncurrify(){constargs=Array.prototype.slice.call(arguments);returnargs.length>=f.length?f.appl

也就是說,線程 1 對共享變量 chenmo 的修改要想被線程 2 及時看到,必須要經過 2 個步驟:

;(InterruptedException e) {          &nb

1、把工作記憶體 1 中更新過的共享變量重新整理到主記憶體中。

2、将主記憶體中最新的共享變量的值更新到工作記憶體 2 中。

。希望本篇文章能夠對大家有所幫助,謝謝大家的閱讀。05、最後謝謝大家的閱讀,原創不易,喜歡就點個贊,這将是我最強的寫作動力。如果你覺得文章對你有所幫助,也蠻有趣的,就關注一下「沉默王二」公衆号。Cur

那假如共享變量沒有及時被其他線程看到的話,會發生什麼問題呢?

eturna+b;}varcurrified=curry(add);即上述currified應該等效為:functioncurrified(a){returnfunction(b){returna+b

public class Wanger {

private static boolean chenmo = false;

public static void main(String[] args) {

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

while (!chenmo) {

}

}

});

thread.start();

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

chenmo = true;

}

}

這段代碼的本意是:在主線程中建立子線程,然後啟動它,當主線程休眠 500 毫秒後,把共享變量 chenmo 的值修改為 true 的時候,子線程中的 while 循環停下來。但運作這段代碼後,程式似乎進入了死循環,過了 N 個 500 毫秒,也沒有要停下來的意思。

tialapplication(withJavaScriptcode)CurryingisnotidiomaticinJavaScriptFunctionalJS#6:Functioncomposit

為什麼會這樣呢?

try {

因為主線程對共享變量 chenmo 的修改沒有及時通知到子線程(子線程在運作的時候,會将 chenmo 變量的值拷貝一份放在自己的工作記憶體當中),當主線程更改了 chenmo 變量的值之後,但是還沒來得及寫入到主存當中,那麼子線程此時就不知道主線程對 chenmo 變量的更改,是以還會一直循環下去。

sleep(500);        } catch (InterruptedException&n

換句話說,就是:普通的共享變量不能保證可見性,因為普通共享變量被修改之後,什麼時候被寫入主記憶體是不确定的,當其他線程去讀取時,此時記憶體中可能還是原來的舊值,是以無法保證可見性。

程擷取鎖然後執行同步代碼,并且在釋放鎖之前會将對變量的修改重新整理到主存當中。關于Lock的更多細節,我們後面再進行讨論。好了,共享變量的可見性就先介紹到這。希望本篇文章能夠對大家有所幫助,謝謝大家的閱讀

那怎麼解決這個問題呢?

urry,functioncurry(fn){//待實作}調用curry後,我們可以得到原函數Curry化後的版本,functionadd(a,b){returna+b;}varcurrified=c

使用 volatile 關鍵字修飾共享變量 chenmo。

ction.lengthWikipedia-CurryingMDN-Function.prototype.bind()Curryingversuspartialapplication(withJava

因為 volatile 變量被線程通路時,會強迫線程從主記憶體中重讀變量的值,而當變量被線程修改時,又會強迫線程将最近的值重新整理到主記憶體當中。這樣的話,線程在任何時候總能看到變量的最新值。

;       thread.start();

我們來使用 volatile 修飾一下共享變量 chenmo。

sp;           Thread.sleep(500);  &

private static volatile boolean chenmo = false;

再次運作代碼後,程式在一瞬間就結束了,500 毫秒畢竟很短啊。在主線程(main 方法)将 chenmo 修改為 true 後,chenmo 變量的值立即寫入到了主記憶體當中;同時,導緻子線程的工作記憶體中緩存變量 chenmo 的副本失效了;當子線程讀取 chenmo 變量時,發現自己的緩存副本無效了,就會去主記憶體讀取最新的值(由 false 變為 true 了),于是 while 循環也就停止了。

,是以volatile并不能為其提供必須的原子特性。除了volatile和synchronized,Lock也能夠保證可見性,它能保證同一時刻隻有一個線程擷取鎖然後執行同步代碼,并且在釋放鎖之前會将對

也就是說,在某種場景下,我們可以使用 volatile 關鍵字來安全地共享變量。這種場景之一就是:狀态真正獨立于程式内地其他内容,比如一個布爾狀态标志(從 false 到 true,也可以再轉換到 false),用于訓示發生了一個重要的一次性事件。

general)的形式,下面看如何實作将任意函數進行Currying化,或偏函數化。将一般化函數進行Currying化我們需要構造這麼一個函數假設名叫curry,functioncurry(fn){/

至于 volatile 的原理和實作機制,本篇不再深入展開了(小編自己沒搞懂,尴尬而不失禮貌的笑一笑)。

mpositionAnelegantandsimplecurry(f)implementationinJavascriptlodash-curryFunction.lengthWikipedia-Cu

需要再次強調地是:

;   }        });    &nbsp

volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 相比,volatile 變量運作時地開銷比較少,但是它所能實作的功能也僅是 synchronized 的一部分(隻能確定可見性,不能確定原子性)。

以被看作是一種“程度較輕的synchronized”;與synchronized相比,volatile變量運作時地開銷比較少,但是它所能實作的功能也僅是synchronized

原子性我們上一篇已經讨論過了,增量操作(i++)看上去像一個單獨操作,但實際上它是一個由“讀取-修改-寫入”組成的序列操作,是以 volatile 并不能為其提供必須的原子特性。

除了 volatile 和 synchronized,Lock 也能夠保證可見性,它能保證同一時刻隻有一個線程擷取鎖然後執行同步代碼,并且在釋放鎖之前會将對變量的修改重新整理到主存當中。關于 Lock 的更多細節,我們後面再進行讨論。

3)(4);//10其中,partial1、partial2、partial3一起構成了原add函數的偏函數。可以看到,偏函數是Curring更加一般(general)的形式,下面看如何實作将任意函數

好了,共享變量的可見性就先介紹到這。希望本篇文章能夠對大家有所幫助,謝謝大家的閱讀。

入參中的各函數,是以取名pipe管道流。以上,函數的組裝。相關資源Whatis"Currying"?CurryandFunctionCompositionAnelegantandsim

05、最後

謝謝大家的閱讀,原創不易,喜歡就點個贊,這将是我最強的寫作動力。如果你覺得文章對你有所幫助,也蠻有趣的,就關注一下「沉默王二」公衆号。

bsp;   }            }&nbs