volatile與synchronized個人理解

在文章之前,首先要理解一個jvm運行時內(nèi)存分配機制以及一些定義。

(一)定義:

** 原子性**:
對于一個操作或者多個操作,要不全部執(zhí)行且在執(zhí)行過程中不可以被中斷,要不直接不執(zhí)行,這種行為成為原子性。

x = 10;         //能保證原子性,10直接寫入x
y = x;         //不能保證原子性,x讀取出來,寫入y
x++;           //不能保證原子性,x讀取出來,自增后,寫入x
x = x + 1;     //不能保證原子性,同上

** 可見性**:
指的是當存在多個線程訪問一個變量的時候,如果其中一個線程改變這個變量值,對于其他線程是可見的,即其他線程可以立即看到這個改變的變量值。

有序性
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行

(二)運行時內(nèi)存分配機制
在其中一個內(nèi)存區(qū)域中有一塊叫jvm虛擬機棧,每個線程都獨立擁有一個線程棧。這里先說兩個定義名詞,但某一對象變量值存儲在某一內(nèi)存空間中,我們這里叫做“主內(nèi)存”,當某一線程訪問該對象變量值,用過對象引用找到該變量值的內(nèi)存空間,每個線程會先復制這個“主內(nèi)存”的變量到自己的線程棧中,等待該線程棧的值操作完成后再更新到主內(nèi)存中,貼下別人整理的圖。

線程棧工作模式.jpg

所以,這里會衍生出一個問題。如果有多個線程讀取這個“主內(nèi)存”的變量,由于每次都是單獨的線程拷貝這個“主內(nèi)存”的變量到自己的線程棧,處理完成后才會更新到“主內(nèi)存”中。所以并發(fā)的情況下,不能保證當前拷貝的值是同步進行的。

volatile

volatile可以解決在線程并發(fā)時候的可見性,即在某一線程棧改變一個主內(nèi)存變量的時候,其他線程棧可以馬上看到這個變化。但是volatile并不能完全保證原子性。下面再舉例說明下為什么不能完全保證原子性。

volatile 的可見性
首先貼下代碼,看下一則比較常見的例子

/**
 * 測試Volatile的可見性demo
 * @author siven
 *
 */
public class VolatileVisible{

    private static int siven = 1;
    public static int getSiven(){
        return siven;
    }
    
    public static void work(){
        siven = 2;
    }
}

public static void main(String[] args) {
        volatileVisibleAction();
    }
    
    private static void volatileVisibleAction(){
        new Thread(new Runnable() {
            
            public void run() {
                VolatileVisible.work();
            }
        }).start();
        
        new Thread(new Runnable() {
            
            public void run() {
                System.out.println("siven : " + VolatileVisible.getSiven());
            }
        }).start();
    }

從實際想要的效果,第一個線程將變量siven更新為2,第二個線程再讀取siven變量并且打印出來。這樣的邏輯是沒有什么問題。但是有一定的可能性存在并發(fā)問題。線程1改變siven變量值的時候,還沒來得及更新到主內(nèi)存,就被線程2
讀取,所以這里有可能讀到的是1。讀者可以多運行幾次,幾率是比較小,截圖如下。

volatile1.png

因此,假如某一線程棧改變了值,其他線程棧可以立即更新的話就可以避免這種問題。
修改成以下代碼:

private volatile static int siven = 1;

所以有volatile的存在,保證線程之間的可見性,當然只能處理多線程并發(fā)讀取問題,為什么這么說呢?看看下面代碼:
VolatileVisible中添加work2方法

public static void work2(){
            siven ++;
}

測試方法

    private static void threadAction(){

        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                
                public void run() {
                    VolatileVisible.work2();
                }
            }).start();
        }


        while(Thread.activeCount()>1)  //保證前面的線程都執(zhí)行完
            Thread.yield();
        System.out.println("siven : " + VolatileVisible.getSiven());

    }

從實際效果中,一萬個線程執(zhí)行完成后,siven會自加一萬次,所以最終想要的結(jié)果應該回事10001,但是經(jīng)過運行觀察,很難可以準確得到10001這個結(jié)果,如圖所示:

volatile2.png

這里讀者會質(zhì)疑,siven已經(jīng)用volatile修飾符修飾了,siven變量改變應該對于所有線程棧是可見才對啊。首先這里我們應該了解siven++;自加操作,其實這個操作并不是原子操作,這里面包括內(nèi)存中取出siven,然后自加1,接著再寫入siven,所以在三個操作中有可能是會被中斷的。雖然用了volatile修飾保證線程棧中的可見性,但是只限制于讀取的時候可見性,也只有線程棧寫入完成之后才會立即更新到主內(nèi)存并且其他線程棧會馬上知道

舉個例子:A線程、B線程都共同訪問siven變量,這時候用得是volatile修飾,因此siven變量對于A、B線程都是可見的。當A對siven變量進行非原子操作(例如自加到2)是有可能出現(xiàn)還沒執(zhí)行到最后,B線程已經(jīng)讀取了舊值1,并且自己也自加到2(原來實際效果是想拿到A線程自加后的結(jié)果,在自加成3)。因此volatile只是保證線程之間的可見性,但是不能保證線程中操作的原子性。所以也引出了synchronized

synchronized

首先在理解synchronized中,要了解對象鎖和類鎖兩個定義。每個java對象都可以用做一個實現(xiàn)同步的鎖,這些鎖成為內(nèi)置鎖。其實對象鎖與類鎖的的存在價值與內(nèi)置鎖一致。只是對象鎖與類鎖的應用場景不一樣,即類鎖應用在靜態(tài)方法,對象鎖應用在實例方法。我們都知道,每個類都存在多個實例化對象,但是每個類只有一個class對象,所以實例化對象中的對象鎖并不會互相干預。

首先貼下代碼:

/**
 * Synchronized 測試方法
 * @author siven
 */

public class SynchronizedWorker {
    
    
    public static void work(String tag){
        
        for (int i = 0; i < 5; i++) {
            System.out.println(tag + " work : " + i);
        }
        
    }

}


private static void synchronizedTest(){
        Thread threadA = new Thread(new Runnable() {

            public void run() {
                SynchronizedWorker.work("A");
            }
        });

        Thread threadB = new Thread(new Runnable() {

            public void run() {
                SynchronizedWorker.work("B");
            }
        });
        
        threadA.start();
        threadB.start();

    }

輸出結(jié)果:

synchronized1.png

首先這里只是輸出了log語句,如果當前不是輸出而是改變某一對象里面的變量的時候,很容易出現(xiàn)因為線程并發(fā)問題導致對象成員變量被改變或者被重新實例化,特別是android中的回調(diào),很多時候是發(fā)生在多線程的,所以很容易發(fā)生這種并發(fā)問題。如果我們要實際的同步效果,我們可以直接使用synchronized對方法或者代碼塊進行加鎖。例如下面的優(yōu)化改造:

(一)直接修飾方法


public synchronized static void work(String tag){
        
        for (int i = 0; i < 5; i++) {
            System.out.println(tag + " work : " + i);
        }
        
    }

(二)修飾代碼塊

    public static void work(String tag){
        
        synchronized (SynchronizedWorker.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(tag + " work : " + i);
            }
        }
        
    }

在synchronized 修飾的方法或者代碼塊中,會將這個區(qū)域進行加鎖,當?shù)谝粋€線程進行訪問的時候,該線程會獲取到該鎖,如果第二個線程對這個區(qū)域進行訪問的時候,如果有其他線程占有的鎖還沒釋放的時候,將會暫時性阻塞,等待其他線程鎖釋放后自己獲取后才可以進行訪問。

當然對于前面volatile可以解決可見性,但是不能完全保證原子性的代碼案例中也可以通過synchronized 解決,我們只需要該成以下代碼即可:

    public static void work2(){
        synchronized (VolatileVisible.class) {
            siven ++;
        }
    }

輸出結(jié)果:


synchronized2.png

注意問題:

前面也說每個類有多個實例化對象,說明有多個對象鎖。如果是synchronized (this)進行代碼塊加鎖,此時只能對當前對象方法進行加鎖,與其他對象鎖沒有任何干預。當然如果是類鎖,針對靜態(tài)方法的,我們沒辦法通過synchronized (this)進行代碼塊加鎖,可以使用synchronized (**.class)進行加鎖

by siven(qq:708854877 email:sy.wu@foxmail.com)

2017.5.26

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容