Java并發(fā)之synchronized關鍵字

?????上篇文章我們主要介紹了并發(fā)的基本思想以及線程的基本知識雕拼,通過多線程我們可以實現(xiàn)對計算機資源的充分利用曼尊,但是在最后我們也說明了多線程給程序帶來的兩種典型的問題菲盾,針對它們昏兆,synchronized關鍵字可以很好的解決問題竿屹。對于synchronized的介紹主要包含以下一些內(nèi)容:

  • synchronized修飾實例方法
  • synchronized修飾靜態(tài)方法
  • synchronized修飾代碼塊
  • 使用synchronized解決競態(tài)條件問題
  • 使用synchronized解決內(nèi)存可見性問題

一报强、使用synchronized關鍵字修飾實例方法
?????在我們的Java中,每個對象都有一把鎖和兩個隊列拱燃,一個用于掛起未獲得鎖的線程秉溉,一個用于掛起條件不滿足而不得不等待的線程。而我們的synchronized實際上也就是一個加鎖和釋放鎖的集成扼雏。先看個例子:

/*定義一個計數(shù)器類*/
public class Counter {
    private int count;

    public synchronized int getCount(){return this.count;}

    public synchronized void addCount(){this.count++;}
}
/*定義一個線程類*/
public class MyThread extends Thread{

    public static Counter counter = new Counter();

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        counter.addCount();
    }
}
/*main方法啟動100個線程*/
public static void main(String[] args){
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.counter.getCount());
    }

上述程序無論運行多少次坚嗜,結(jié)果都是一樣的。

這里寫圖片描述

這是一個典型的使用synchronized關鍵字修飾實例方法來解決競態(tài)條件問題的示例诗充。首先在我們定義的線程類中苍蔬,我們定義了一個Counter實例,然后讓以后的每個線程在運行的時候都先隨機睡眠蝴蜓,然后調(diào)用這個公共變量count的自增方法碟绑,只不過該自增方法是有synchronized關鍵字修飾的俺猿。我們說過每個對象都有鎖和兩個隊列,這里的count實例就是一個對象格仲,這一百個線程每次在睡醒之后都要調(diào)用count的addCount方法押袍,而所有要調(diào)用addCount方法的線程都必須先獲得count這個對象的鎖,也就是說凯肋,如果有一個線程獲取了count對象的鎖并開始調(diào)用addCount方法時谊惭,其他線程都得阻塞在該對象的一個隊列上,等待獲得鎖的線程執(zhí)行結(jié)束釋放鎖侮东。

所以圈盔,在同一時刻,只可能有一個線程獲得count的鎖并對其進行自增操作悄雅,其他的線程都在該對象的阻塞隊列上進行等待驱敲,自然是不會出現(xiàn)多個線程在某個時間段同時操作同一個變量而引起該變量數(shù)據(jù)值不正確的情況。

二宽闲、使用synchronized關鍵字修飾靜態(tài)方法
?????對于靜態(tài)方法众眨,其實和實例方法是類似的。只不過synchronized關鍵字對實例方法而言容诬,它獲得的是實例對象的鎖娩梨,所有共享相同該對象的線程都必須先獲得該對象的鎖。而對于靜態(tài)方法而言放案,synchronized關鍵字獲得的是類的鎖姚建,也就是對于所有需要訪問相同類的線程都是需要先獲得該類的鎖的,否則將需要在某個阻塞隊列上進行等待吱殉。

/*定義一個線程類*/
public class MyThread extends Thread{

    public static int count;

    public synchronized static void addCount(){
        count++;
    }
    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        addCount();
    }
}
/*啟動100個線程*/
public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.count);
    }

程序基本和我們的第一個例子相差無幾,在線程類中我們定義了一個靜態(tài)變量和一個靜態(tài)方法厘托,該方法被synchronized關鍵字修飾友雳,然后run方法依然是讓當前線程隨機睡眠,然后調(diào)用這個被synchronized關鍵字修飾的靜態(tài)方法铅匹。我們可以看到押赊,無論運行多少次的程序,結(jié)果都是一樣包斑。

這里寫圖片描述

每個線程在睡醒之后流礁,都要去調(diào)用addCount方法,而調(diào)用該方法前提是要獲取到類Count的鎖罗丰,如果獲取不到就必須在該對象的阻塞隊列上進行等待神帅。所以一次只會有一個線程調(diào)用addCount方法,自然是無論運行多少次萌抵,結(jié)果都會是100找御。

三元镀、使用synchronized關鍵字修飾代碼塊
?????使用synchronized關鍵字修飾一段代碼塊和上述介紹的兩種情況略微有點不同。對于實例方法霎桅,synchronized關鍵字總是嘗試去獲取某個對象的鎖栖疑,對于靜態(tài)方法,synchronized關鍵字始終嘗試去獲取某個類的鎖滔驶,而對于我們的代碼塊遇革,它就需要顯式指定以誰為鎖了。例如:

/*定義一個線程類*/
public class MyThread extends Thread{

    public static Integer count = 0;

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (count){
            count++;
        }
    }
}

在我們定義的線程類中揭糕,我們定義了一個靜態(tài)變量count澳淑,而每個線程在醒來之后都會去嘗試著去獲取該對象的鎖,如果得不到就阻塞在該對象的阻塞隊列上等待鎖的釋放插佛。實際上這里的synchronized關鍵字利用的就是對象count的鎖杠巡,我們上述介紹的兩種形式,synchronized關鍵字修飾在實例方法和靜態(tài)方法上雇寇,默認利用的是類對象的鎖和類的鎖氢拥。例如:

public synchronized void show(){....} 

調(diào)用show方法等價于:

synchronized(this){
    public void show(){...}
}

而對于靜態(tài)方法:

public class A{
    public synchronized static void show(){....}
}

等價于:

synchronized(A.class){
    public static void show(){....}
}

四、使用synchronized關鍵字解決內(nèi)存可見性問題
?????通過了解了synchronized應用的三種不同場景锨侯,我們對它應該有了大致的一個了解嫩海。下面我們使用它解決上篇提到的多線程的一個問題 ----- 內(nèi)存可見性問題。至于競態(tài)條件問題已經(jīng)在第一小節(jié)間接的進行介紹了囚痴,此處不再贅述叁怪。這里我們再簡單重復下內(nèi)存可見性問題,因為我們的CPU是有緩存的深滚,所以當一個線程在運行的時候奕谭,有些變量值的修改并沒有立馬寫回內(nèi)存,而是緩存在各級緩存中痴荐,這就導致其他線程訪問這個公共變量的時候就拿不到最新的值血柳,因此導致數(shù)據(jù)的值偏差,計算結(jié)果不準確生兆。我們看看一個例子:

/*定義一個線程類难捌,并定義一個共享的變量count*/
public class MyThread extends Thread{

    public static int count = 0;

    @Override
    public void run(){
        while (count==0){
            //running
        }
        System.out.println("mythread exit");
    }
}
/*main函數(shù)啟動一個線程*/
public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();

        Thread.sleep(1000);

        MyThread.count = 1;
        System.out.println(MyThread.count);
        System.out.println("exit main");

    }

我們在定義的線程類中定義了一個共享變量,run方法主要的工作是循環(huán)等待count不為0鸦难,而我們在main線程中修改了這個count的值根吁,由于循環(huán)這個操作是比較頻繁的判斷條件的,所以該線程并不會每次都從內(nèi)存中取出count的值合蔽,而是在它的緩存中取击敌,所以主線程對count的修改,在thread線程中是始終看不見的辈末。所以我們的程序輸出的結(jié)果如下:

這里寫圖片描述

主線程在修改count的值之后愚争,輸出顯示的確count的值為1映皆,然后主線程退出,但是我們發(fā)現(xiàn)程序卻沒有結(jié)束轰枝,thread的退出信息也沒有被打印捅彻。也就是說線程thread還被困在了while循環(huán)中,雖然main線程已經(jīng)修改了count的值鞍陨。這就是內(nèi)存可見性問題步淹,主要是由于多線程之間進行通訊的橋梁是內(nèi)存,而各個線程內(nèi)部又有各自的緩存诚撵,如果對公共變量的的修改沒有及時更新到內(nèi)存的話缭裆,那么就很容易導致其他線程訪問的是數(shù)據(jù)不是最新的。

我們使用synchronized關鍵字解決上述問題:

public class MyThread extends Thread{

    public static int count = 0;

    public synchronized static int returnCount(){return count;}
    
    @Override
    public void run(){
        while(returnCount()==0){

        }
        System.out.println("mythread exit");
    }
}

我們使用synchronized關鍵修飾了一個方法寿烟,該方法返回count的值澈驼。jvm對synchronized的兩條規(guī)定,其一是線程在解鎖之前必須把所有共享變量刷新到內(nèi)存中筛武,其二是線程在釋放鎖的時候?qū)⑶蹇账械木彺嫫仁贡揪€程在使用該共享變量的時候從內(nèi)存中去讀取缝其。這樣就可以保證每次對共享變量的讀取都是最新的。

當然如果僅僅是為了解決內(nèi)存可見性問題而使用synchronized關鍵字的話徘六,會有點大材小用内边。畢竟synchronized的成本開銷相對而言是較大的。Java中提供了一個volatile關鍵字用于解決這種內(nèi)存可見性問題待锈。例如:

public static volatile int count = 0;

像這樣漠其,我們只需要在某個變量前面加上修飾符 volatile 即可讓該變量在被讀的時候從內(nèi)存去取,也就是保持最新數(shù)據(jù)值以實現(xiàn)對內(nèi)存可見性問題的解決竿音。

至此和屎,我們簡單的介紹了synchronized關鍵字的一些基本用法,介紹了它可以修飾的場景谍失,以及使用它來解決我們的兩個典型的多線程問題眶俩。下篇文章我們將著重介紹線程間的協(xié)作機制。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末快鱼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纲岭,更是在濱河造成了極大的恐慌抹竹,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件止潮,死亡現(xiàn)場離奇詭異窃判,居然都是意外死亡,警方通過查閱死者的電腦和手機喇闸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門袄琳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來询件,“玉大人,你說我怎么就攤上這事唆樊⊥鹄牛” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵逗旁,是天一觀的道長嘿辟。 經(jīng)常有香客問我,道長片效,這世上最難降的妖魔是什么红伦? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮淀衣,結(jié)果婚禮上昙读,老公的妹妹穿的比我還像新娘。我一直安慰自己膨桥,他們只是感情好蛮浑,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著国撵,像睡著了一般陵吸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上介牙,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天壮虫,我揣著相機與錄音,去河邊找鬼环础。 笑死囚似,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的线得。 我是一名探鬼主播饶唤,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贯钩!你這毒婦竟也來了募狂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤角雷,失蹤者是張志新(化名)和其女友劉穎祸穷,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勺三,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡雷滚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吗坚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈远。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡呆万,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出车份,到底是詐尸還是另有隱情谋减,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布躬充,位于F島的核電站逃顶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏充甚。R本人自食惡果不足惜以政,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伴找。 院中可真熱鬧盈蛮,春花似錦、人聲如沸技矮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衰倦。三九已至袒炉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樊零,已是汗流浹背我磁。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驻襟,地道東北人夺艰。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像沉衣,于是被迫代替她去往敵國和親郁副。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361

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