你是否真正理解并會用volatile, synchronized, final進行線程間通信呢,如果你不能回答下面的幾個問題,那就說明你并沒有真正的理解:
1、對volatile變量的操作一定具有原子性嗎?(原子操作是不需要synchronized。所謂原子操作是指不會被線程調(diào)度機制打斷的操作;這種操作一旦開始,就一直運行到結(jié)束,中間不會有任何線程切換)
2、synchronized所謂的加鎖,鎖住的是什么?
3、final定義的變量不變的到底是什么?
看Java內(nèi)存模型之前,我們先來了解什么是內(nèi)存模型?
在多處理器系統(tǒng)中,處理器通常有多級緩存,因為這些緩存離處理器更近并且可以存儲一部分數(shù)據(jù),所以緩存可以改善處理器獲取數(shù)據(jù)的速度和減少對共享內(nèi) 存數(shù)據(jù)總線的占用。緩存雖然能極大的提高性能,但是同時也帶來了諸多挑戰(zhàn)。例如,當兩個處理器同時操作同一個內(nèi)存地址的時候,該如何處理?這兩個處理器在 什么條件下才能看到相同的值?
對于處理器而言,一個內(nèi)存模型就是定義一些充分必要的規(guī)范,這些規(guī)范使得其他處理器對內(nèi)存的寫操作對當前處理器可見,或者當前處理器的寫操作對其他處理器可見。
其他處理器對內(nèi)存的寫一定發(fā)生在當前處理器對同一內(nèi)存的讀之前,稱之為其他處理器對內(nèi)存的寫對當前處理器可見。
知道了內(nèi)存模型,那么應該可以更好的理解java內(nèi)存模型。簡單的講,java內(nèi)存模型指的就是一套規(guī)范,現(xiàn)在最新的規(guī)范為JSR-133。這套規(guī)范包含:
1、線程之間如何通過內(nèi)存通信;
2、線程之間通過什么方式通信才合法,才能得到期望的結(jié)果。
我們已經(jīng)知道 java 內(nèi)存模型就是一套規(guī)范,那么在這套規(guī)范中,規(guī)定的內(nèi)存結(jié)構(gòu)是什么樣的呢?java內(nèi)存模型中的內(nèi)存結(jié)構(gòu)如下圖所示:

簡單的講,Java 內(nèi)存模型將內(nèi)存分為共享內(nèi)存和本地內(nèi)存。共享內(nèi)存又稱為堆內(nèi)存,指的就是線程之間共享的內(nèi)存,包含所有的實例域、靜態(tài)域和數(shù)組元素。每個線程都有一個私有的,只對自己可見的內(nèi)存,稱之為本地內(nèi)存。
共享內(nèi)存中共享變量雖然由所有的線程共享,但是為了提高效率,線程并不直接使用這些變量,每個線程都會在自己的本地內(nèi)存中存儲一個共享內(nèi)存的副本,使用這個副本參與運算。由于這個副本的參與,導致了線程之間對共享內(nèi)存的讀寫存在可見性問題。
為了方便線程之間的通信,java 提供了 volatile, synchronized, final 三個關鍵字供我們使用,下面我們來看看如何使用它們進行線程間通信。
一個線程對 volatile 變量的寫一定對之后對這個變量的讀的線程可見。
等價于
一個線程對 volatile 變量的讀一定能看見在它之前最后一個線程對這個變量的寫。
為了實現(xiàn)這些語義,Java 規(guī)定,(1)當一個線程要使用共享內(nèi)存中的 volatile 變量時,如圖中的變量a,它會直接從主內(nèi)存中讀取,而不使用自己本地內(nèi)存中的副本。(2)當一個線程對一個 volatile 變量進行寫時,它會將這個共享變量的值刷新到共享內(nèi)存中。
我們可以看到,其實 volatile 變量保證的是一個線程對它的寫會立即刷新到主內(nèi)存中,并置其它線程的副本為無效,它并不保證對 volatile 變量的操作都是具有原子性的。
public void add(){ a++; #1 }等價于public void add() { temp = a; temp = temp +1; a = temp; }代碼1并不是一個原子操作,所以類似于 a++ 這樣的操作會導致并發(fā)數(shù)據(jù)問題。volatile 變量的寫被保證是可以被之后其他線程的讀看到的,因此我們可以利用它進行線程間的通信。如:
volatile int a;public void set(int b) { a = b; }public void get() { int i = a; }線程A執(zhí)行set()后,線程B執(zhí)行get(),相當于線程A向線程B發(fā)送了消息。synchronized
如果我們非要使用 a++ 這種復合操作進行線程間通信呢?java 為我們提供了synchronized。public synchronized void add() { a++; }synchronized 使得它作用范圍內(nèi)的代碼對于不同線程是互斥的,并且線程在釋放鎖的時候會將共享變量的值刷新到主內(nèi)存中。我們可以利用這種互斥性來進行線程間通信??聪旅娴拇a,public synchronized void add() { a++; }public synchronized void get() { int i = a; }當線程A執(zhí)行 add(),線程B調(diào)用get(),由于互斥性,線程A執(zhí)行完add()后,線程B才能開始執(zhí)行get(),并且線程A執(zhí)行完add(),釋放鎖的時候,會將a的值刷新到共享內(nèi)存中。因此線程B拿到的a的值是線程A更新之后的。volatile 和 synchronized比較
根據(jù)以上的分析,我們可以發(fā)現(xiàn)volatile和synchronized有些相似。1、當線程對 volatile變量寫時,java 會把值刷新到共享內(nèi)存中;而對于synchronized,指的是當線程釋放鎖的時候,會將共享變量的值刷新到主內(nèi)存中。
2、線程讀取volatile變量時,會將本地內(nèi)存中的共享變量置為無效;對于synchronized來說,當線程獲取鎖時,會將當前線程本地內(nèi)存中的共享變量置為無效。
3、synchronized 擴大了可見影響的范圍,擴大到了synchronized作用的代碼塊。
final 變量
final關鍵字可以作用于變量、方法和類,我們這里只看final 變量。final變量的特殊之處在于:final 變量一經(jīng)初始化,就不能改變其值。
這里的值對于一個對象或者數(shù)組來說指的是這個對象或者數(shù)組的引用地址。因此,一個線程定義了一個final變量之后,其他任意線程都拿到這個變量。但有一點需要注意的是,當這個final變量為對象或者數(shù)組時,
1、雖然我們不能講這個變量賦值為其他對象或者數(shù)組,但是我們可以改變對象的域或者數(shù)組中的元素。
2、線程對這個對象變量的域或者數(shù)據(jù)的元素的改變不具有線程可見性。
新聞熱點
疑難解答