多線程安全-sychronized


layout: post
title: "多線程安全-sychronized"
categories: [編程]
tags: [Java,多線程]
published: True


造成線程數(shù)據(jù)錯亂的三要素 ( 同時也是保持線程安全的要素 )

名詞解釋

  • 原子性(Synchronized, Lock)即一個操作或者多個操作甩骏,要么執(zhí)行 要么就都不執(zhí)行抄伍,在執(zhí)行過程中不可打斷
  • 有序性 (Volatile,Synchronized, Lock) 程序默認(rèn)的執(zhí)行順序
  • 可見性 (Volatile集晚,Synchronized,Lock) 當(dāng)變量被修改時,所修改的值會被立即同步到主存中蜓谋,其他程序或者進(jìn)程再讀取時獲取的值是最新的

synchronized的三種應(yīng)用方式

  • 修飾實(shí)例方法梦皮,作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
public class AccountingSync implements Runnable{
    //共享資源(臨界資源)
    static int i=0;

    /**
     * synchronized 修飾實(shí)例方法
     * 暫時先不添加
     */
    public void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AccountingSync instance=new AccountingSync();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    /**
     * 輸出結(jié)果:
     * 1802452
     */
}

i++ 賦值操作并沒有 原子性 在讀取原來的參數(shù)和返回新的參數(shù)的這段時間中桃焕,如果我們第二個線程也做了同樣的操作剑肯,兩個線程看到的參數(shù)是一樣的,同樣執(zhí)行了 +1 的操作观堂,這里就造成了線程安全破壞让网,我們最終輸出的結(jié)果是 1802452

如果我們添加上 synchronized 此時 increase() 方法在一個時間內(nèi) 只能被一個線程讀寫,也就避免臟數(shù)據(jù)的產(chǎn)生师痕。

但是這樣也不是安全的溃睹,如果我們 new 出兩個 AccountingSync 對象去執(zhí)行操作 結(jié)果是怎么樣?

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新實(shí)例
        Thread t1=new Thread(new AccountingSyncBad());
        //new新實(shí)例
        Thread t2=new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        //join含義:當(dāng)前線程A等待thread線程終止之后才能從thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

結(jié)果依舊是產(chǎn)生了臟數(shù)據(jù)七兜,原因是兩個實(shí)例對象鎖并不同相同丸凭,此時如果兩個線程操作數(shù)據(jù)并非共享的,線程安全是有保障的腕铸,遺憾的是如果兩個線程操作的是共享數(shù)據(jù)惜犀,安全將沒有保障,他們本身的鎖只能保持本身

  • 修飾靜態(tài)方法狠裹,作用于當(dāng)前類對象加鎖虽界,進(jìn)入同步代碼前要獲得當(dāng)前類對象的鎖

    public class AccountingSyncClass implements Runnable{
        static int i=0;
    
        /**
         * 作用于靜態(tài)方法,鎖是當(dāng)前class對象,也就是
         * AccountingSyncClass類對應(yīng)的class對象
         */
        public static synchronized void increase(){
            i++;
        }
    
        /**
         * 非靜態(tài),訪問時鎖不一樣不會發(fā)生互斥
         */
        public synchronized void increase4Obj(){
            i++;
        }
    
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();
            }
        }
        public static void main(String[] args) throws InterruptedException {
            //new新實(shí)例
            Thread t1=new Thread(new AccountingSyncClass());
            //new心事了
            Thread t2=new Thread(new AccountingSyncClass());
            //啟動線程
            t1.start();t2.start();
    
            t1.join();t2.join();
            System.out.println(i);
        }
    }
    

    這個實(shí)例當(dāng)中我們將 synchronized 作用于靜態(tài)方法了,因?yàn)殪o態(tài)方法不屬于任何實(shí)例對象,它是類成員涛菠,所以這把鎖也可以理解為加在了 Class 上 莉御,但是如果我們線程A 調(diào)用了 class 內(nèi)部 static synchronized 方法 線程B 調(diào)用了 class 內(nèi)部 非 static 方法 是可以的,不會發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的class對象俗冻,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對象鎖

  • 修飾代碼塊礁叔,指定加鎖對象,對給定對象加鎖迄薄,進(jìn)入同步代碼庫前要獲得給定對象的鎖琅关。

    public class AccountingSync implements Runnable{
        static AccountingSync instance=new AccountingSync();
        static int i=0;
        @Override
        public void run() {
            //省略其他耗時操作....
            //使用同步代碼塊對變量i進(jìn)行同步操作,鎖對象為instance
            synchronized(instance){
                for(int j=0;j<1000000;j++){
                        i++;
                  }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread t1=new Thread(instance);
            Thread t2=new Thread(instance);
            t1.start();t2.start();
            t1.join();t2.join();
            System.out.println(i);
        }
    }
    

synchronized 是如何實(shí)現(xiàn)的同步

同步代碼塊

內(nèi)部使用 monitorenter 和 monitorexit 指令實(shí)現(xiàn),monitorenter 指令插入到同步代碼塊的開始位置讥蔽,monitorexit 指令插入到同步代碼塊的結(jié)束位置涣易,jvm需要保證每一個monitorenter都有一個 monitorexit 與之對應(yīng)。任何一個對象都有一個 monitor 與之相關(guān)聯(lián)冶伞,當(dāng)它的monitor被持有之后新症,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令前响禽,將會嘗試獲取對象所對應(yīng)的 monitor 所有權(quán)徒爹,即嘗試獲取對象的鎖荚醒;將線程執(zhí)行到 monitorexit 時就會釋放鎖。

(人話:)每個對象都會與一個monitor相關(guān)聯(lián)隆嗅,當(dāng)某個monitor被擁有之后就會被鎖住腌且,當(dāng)線程執(zhí)行到monitorenter指令時,就會去嘗試獲得對應(yīng)的monitor榛瓮。步驟如下:

  1. 每個monitor維護(hù)著一個記錄著擁有次數(shù)的計數(shù)器铺董。未被擁有的monitor的計數(shù)器為0,當(dāng)一個線程獲得monitor(執(zhí)行monitorenter)后禀晓,該計數(shù)器自增變?yōu)?1 精续。當(dāng)同一個線程再次獲得該monitor的時候,計數(shù)器再次自增粹懒;當(dāng)不同線程想要獲得該monitor的時候重付,就會被阻塞。

  2. 當(dāng)同一個線程釋放monitor(執(zhí)行monitorexit指令)的時候凫乖,計數(shù)器再自減确垫。當(dāng)計數(shù)器為0的時候。monitor將被釋放帽芽,其他線程便可以獲得monitor删掀。


    image

同步方法

不過相對于普通方法,其常量池中多了ACC_SYNCHRONIZED標(biāo)示符导街。JVM就是根據(jù)該標(biāo)示符來實(shí)現(xiàn)方法的同步的:當(dāng)方法調(diào)用時披泪,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了搬瑰,執(zhí)行線程將先獲取monitor款票,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor泽论。

在jvm字節(jié)碼層面并沒有任何特別的指令來實(shí)現(xiàn)synchronized修飾的方法艾少,而是在class文件中將該方法的access_flags字段中的acc_synchronized標(biāo)志位設(shè)置為1,表示該方法為synchronized方法翼悴。

在java設(shè)計中缚够,每一個對象自打娘胎里出來就帶了一把看不見的鎖,即monitor鎖抄瓦。monitor是線程私有的數(shù)據(jù)結(jié)構(gòu)潮瓶,每一個線程都有一個monitor record列表陶冷,同時還有一個全局可用列表钙姊。每一個被鎖住對象都會和一個monitor關(guān)聯(lián)。monitor中有一個owner字段存放擁有該對象的線程的唯一標(biāo)識埂伦,表示該鎖被這個線程占有煞额。owner:初始時為null,表示當(dāng)前沒有任何線程擁有該monitor,當(dāng)線程成功擁有該鎖后膊毁,owner保存線程唯一標(biāo)識胀莹,當(dāng)鎖被釋放時,owner又變?yōu)閚ull婚温。


image

總結(jié):
同步方法和同步代碼塊底層都是通過monitor來實(shí)現(xiàn)同步的描焰。兩者的區(qū)別:同步方式是通過方法中的access_flags中設(shè)置ACC_SYNCHRONIZED標(biāo)志來實(shí)現(xiàn);同步代碼塊是通過monitorenter和monitorexit來實(shí)現(xiàn)我們知道了每個對象都與一個monitor相關(guān)聯(lián)栅螟。而monitor可以被線程擁有或釋放荆秦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市力图,隨后出現(xiàn)的幾起案子步绸,更是在濱河造成了極大的恐慌,老刑警劉巖吃媒,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓤介,死亡現(xiàn)場離奇詭異,居然都是意外死亡赘那,警方通過查閱死者的電腦和手機(jī)刑桑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來募舟,“玉大人漾月,你說我怎么就攤上這事∥刚洌” “怎么了梁肿?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長觅彰。 經(jīng)常有香客問我吩蔑,道長,這世上最難降的妖魔是什么填抬? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任烛芬,我火速辦了婚禮,結(jié)果婚禮上飒责,老公的妹妹穿的比我還像新娘赘娄。我一直安慰自己,他們只是感情好宏蛉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布遣臼。 她就那樣靜靜地躺著,像睡著了一般拾并。 火紅的嫁衣襯著肌膚如雪揍堰。 梳的紋絲不亂的頭發(fā)上鹏浅,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音屏歹,去河邊找鬼隐砸。 笑死,一個胖子當(dāng)著我的面吹牛蝙眶,可吹牛的內(nèi)容都是我干的季希。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幽纷,長吁一口氣:“原來是場噩夢啊……” “哼胖眷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霹崎,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤珊搀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尾菇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體境析,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年派诬,在試婚紗的時候發(fā)現(xiàn)自己被綠了劳淆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡默赂,死狀恐怖沛鸵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缆八,我是刑警寧澤曲掰,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站奈辰,受9級特大地震影響栏妖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奖恰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一吊趾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瑟啃,春花似錦论泛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜡峰,卻和暖如春了袁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背湿颅。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工载绿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人油航。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓崭庸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谊囚。 傳聞我的和親對象是個殘疾皇子怕享,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

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