Java 多線程(1): synchronized關(guān)鍵字詳解

整理自博客:http://www.cnblogs.com/mengdd/archive/2013/02/16/2913806.html


0筒狠、“同步”的概述

多線程的同步機制是用來對資源進行加鎖的,使得在同一個時間省店,只有一個線程可以對某一對象進行操作粪小,同步用以解決多個線程同時訪問時可能出現(xiàn)的問題齐邦。

  • 同步機制可以使用synchronized關(guān)鍵字實現(xiàn)锰蓬。
  • synchronized關(guān)鍵字修飾一個方法的時候锣险,該方法就叫做同步方法旬昭。
  • synchronized方法執(zhí)行完或發(fā)生異常時篙螟,會自動釋放鎖。

下面通過一個例子來對synchronized關(guān)鍵字的用法進行解析稳懒。

1闲擦、使用synchronized關(guān)鍵字與不使用synchronized關(guān)鍵字的區(qū)別

public class ThreadTest
{
    public static void main(String[] args)
    {
        Example example = new Example();
        Thread t1 = new Thread1(example);
        Thread t2 = new Thread1(example);
        t1.start();
        t2.start();
    }
}

class Example
{
    public synchronized void execute()
    {
        for (int i = 0; i < 10; ++i)
        {
            try
            {
                Thread.sleep(500);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("Hello: " + i);
        }
    }
}

class Thread1 extends Thread
{
    private Example example;
    public Thread1(Example example)
    {
        this.example = example;
    }
    @Override
    public void run()
    {
        example.execute();
    }

}

是否在execute()方法前加上synchronized關(guān)鍵字,這個例子程序的執(zhí)行結(jié)果會有很大的不同场梆。
如果不加synchronized關(guān)鍵字,則兩個線程同時執(zhí)行execute()方法纯路,輸出是兩組并發(fā)的或油。
如果加上synchronized關(guān)鍵字
,則會先輸出一組0到9驰唬,然后再輸出下一組顶岸,說明兩個線程是順次執(zhí)行的腔彰。

2、多個方法同時使用synchronized關(guān)鍵字修飾

將程序改動一下辖佣,Example類中再加入一個方法execute2()霹抛。
之后再寫一個線程類Thread2,Thread2中的run()方法執(zhí)行的是execute2()卷谈。Example類中的兩個方法都是被synchronized關(guān)鍵字修飾的杯拐。

public class ThreadTest
{
    public static void main(String[] args)
    {
        Example example = new Example();

        Thread t1 = new Thread1(example);
        Thread t2 = new Thread2(example);

        t1.start();
        t2.start();
    }

}

class Example
{
    public synchronized void execute()
    {
        for (int i = 0; i < 20; ++i)
        {
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("Hello: " + i);
        }
    }

    public synchronized void execute2()
    {
        for (int i = 0; i < 20; ++i)
        {
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("World: " + i);
        }
    }

}

class Thread1 extends Thread
{
    private Example example;

    public Thread1(Example example)
    {
        this.example = example;
    }

    @Override
    public void run()
    {
        example.execute();
    }

}

class Thread2 extends Thread
{
    private Example example;

    public Thread2(Example example)
    {
        this.example = example;
    }

    @Override
    public void run()
    {
        example.execute2();
    }

}

如果沒有synchronized關(guān)鍵字,則兩個方法并發(fā)執(zhí)行世蔗,并沒有相互影響端逼。
但是如例子程序中所寫,即便是兩個方法污淋,執(zhí)行結(jié)果永遠是執(zhí)行完一個線程的輸出再執(zhí)行另一個線程的顶滩。

這說明:如果一個對象有多個synchronized方法,某一時刻某個線程已經(jīng)進入到了某個synchronized方法寸爆,那么在該方法沒有執(zhí)行完畢前礁鲁,其他線程是無法訪問該對象的任何synchronized方法的。

結(jié)論:

當synchronized關(guān)鍵字修飾一個方法的時候赁豆,該方法叫做同步方法救氯。
Java中的每個對象都有一個鎖(lock),或者叫做監(jiān)視器(monitor)歌憨,當一個線程訪問某個對象的synchronized方法時着憨,將該對象上鎖,其他任何線程都無法再去訪問該對象的synchronized方法了(這里是指所有的同步方法务嫡,而不僅僅是同一個方法)甲抖,直到之前的那個線程執(zhí)行方法完畢后(或者是拋出了異常),才將該對象的鎖釋放掉心铃,其他線程才有可能再去訪問該對象的synchronized方法准谚。
注意這時候是給對象上鎖,如果是不同的對象去扣,則各個對象之間沒有限制關(guān)系柱衔。
嘗試在代碼中構(gòu)造第二個線程對象時傳入一個新的Example對象,則兩個線程的執(zhí)行之間沒有什么制約關(guān)系愉棱。

3.靜態(tài)方法被synchronized關(guān)鍵字修飾

當一個synchronized關(guān)鍵字修飾的方法同時又被static修飾唆铐,之前說過,非靜態(tài)的同步方法會將對象上鎖奔滑,但是靜態(tài)方法不屬于對象艾岂,而是屬于類,它會將這個方法所在的類的Class對象上鎖朋其。
一個類不管生成多少個對象王浴,它們所對應的是同一個Class對象脆炎。

public class ThreadTest
{
    public static void main(String[] args)
    {
        Example example = new Example();
        Thread t1 = new Thread1(example);
        // 此處即便傳入不同的對象,靜態(tài)方法同步仍然不允許多個線程同時執(zhí)行
        example = new Example();
        Thread t2 = new Thread2(example);
        t1.start();
        t2.start();
    }
}

class Example
{
    public synchronized static void execute()
    {
        for (int i = 0; i < 20; ++i)
        {
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("Hello: " + i);
        }
    }

    public synchronized static void execute2()
    {
        for (int i = 0; i < 20; ++i)
        {
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("World: " + i);
        }
    }

}

class Thread1 extends Thread
{
    private Example example;

    public Thread1(Example example)
    {
        this.example = example;
    }
    @Override
    public void run()
    {
        Example.execute();
    }

}

class Thread2 extends Thread
{
    private Example example;
    public Thread2(Example example)
    {
        this.example = example;
    }
   @Override
    public void run()
    {
        Example.execute2();
    }
}

所以如果是靜態(tài)同步方法(execute()和execute2()都加上static關(guān)鍵字)氓辣,即便是向兩個線程傳入不同的Example對象秒裕,這兩個線程仍然是互相制約的,必須先執(zhí)行完一個钞啸,再執(zhí)行下一個几蜻。

結(jié)論:

如果某個synchronized方法是static的,那么當線程訪問該方法時爽撒,它鎖的并不是synchronized方法所在的對象入蛆,而是synchronized方法所在的類所對應的Class對象。Java中硕勿,無論一個類有多少個對象哨毁,這些對象會對應唯一一個Class對象,因此當線程分別訪問同一個類的兩個對象的兩個static源武,synchronized方法時扼褪,它們的執(zhí)行順序也是順序的,也就是說一個線程先去執(zhí)行方法粱栖,執(zhí)行完畢后另一個線程才開始话浇。

4. synchronized塊

synchronized塊寫法:

synchronized(object)
{      
}

這個表示線程在執(zhí)行的時候會將object對象上鎖。(注意這個對象可以是任意類的對象闹究,也可以使用this關(guān)鍵字)幔崖。這樣就可以自行規(guī)定上鎖對象。

public class ThreadTest
{
    public static void main(String[] args)
    {
        Example example = new Example();
        Thread t1 = new Thread1(example);
        Thread t2 = new Thread2(example);
        t1.start();
        t2.start();
    }

}
class Example
{
    private Object object = new Object();

    public void execute()
    {
        synchronized (object)
        {
            for (int i = 0; i < 20; ++i)
            {
                try
                {
                    Thread.sleep((long) Math.random() * 1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("Hello: " + i);
            }

        }

    }
    public void execute2()
    {
        synchronized (object)
        {
            for (int i = 0; i < 20; ++i)
            {
                try
                {
                    Thread.sleep((long) Math.random() * 1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println("World: " + i);
            }
        }
    }
}
class Thread1 extends Thread
{
    private Example example;
    public Thread1(Example example)
    {
        this.example = example;
    }
  @Override
    public void run()
    {
        example.execute();
    }
}
class Thread2 extends Thread
{
    private Example example;
    public Thread2(Example example)
    {
        this.example = example;
    }
    @Override
    public void run()
    {
        example.execute2();
    }
}
  • synchronized方法實際上等同于用一個synchronized塊包住方法中的所有語句渣淤,然后在synchronized塊的括號中傳入this關(guān)鍵字赏寇。當然,如果是靜態(tài)方法价认,需要鎖定的則是class對象嗅定。

如下為鎖類與鎖對象的區(qū)別:
鎖整個類:注意lock對象是靜態(tài)的

public class TestThread {  
     private static Object lock=new Object(); //必須是靜態(tài)的。  
     public  void execute(){  
         synchronized(lock){  
             for(int i=0;i<100;i++){  
                 System.out.println(i);  
             }      
         }  
     }  
}  

鎖單個對象:

  public class TestThread {  
    private Object lock=new Object();   
    public  void execute(){  
        synchronized(lock){  
            //增加了個鎖用踩,鎖定了對象lock渠退,在同一個類實例中,是線程安全的脐彩,但不同的實例還是不安全的碎乃。  因為不同的實例有不同對象鎖lock  
            for(int i=0;i<100;i++){  
                   System.out.println(i);  
               }      
           }  
       }  
}    

其實上面鎖定一個方法,等同于下面的:

public void execute(){    
    synchronized(this){   //同步的是當然對象  
        for(int i=0;i<100;i++){  
               System.out.println(i);  
           }      
       }  
}  

這里我們會有疑問丁屎,為啥有了synchronized關(guān)鍵字還要有這么一個synchronized塊荠锭,原因就是可能一個方法中我們只有幾行代碼會涉及到線程同步問題,所以synchronized塊比synchronized方法更加細粒度地控制了多個線程的訪問晨川,只有synchronized塊中的內(nèi)容不能同時被多個線程所訪問证九,方法中的其他語句仍然可以同時被多個線程所訪問(包括synchronized塊之前的和之后的)。
  注意:被synchronized保護的數(shù)據(jù)應該是私有的共虑。
  
總結(jié):
  synchronized方法是一種粗粒度的并發(fā)控制愧怜,某一時刻,只能有一個線程執(zhí)行該synchronized方法妈拌;
  synchronized塊則是一種細粒度的并發(fā)控制拥坛,只會將塊中的代碼同步,位于方法內(nèi)尘分、synchronized塊之外的其他代碼是可以被多個線程同時訪問到的猜惋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市培愁,隨后出現(xiàn)的幾起案子著摔,更是在濱河造成了極大的恐慌,老刑警劉巖定续,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谍咆,死亡現(xiàn)場離奇詭異,居然都是意外死亡私股,警方通過查閱死者的電腦和手機摹察,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倡鲸,“玉大人供嚎,你說我怎么就攤上這事∏妥矗” “怎么了克滴?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宁炫。 經(jīng)常有香客問我偿曙,道長,這世上最難降的妖魔是什么羔巢? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任望忆,我火速辦了婚禮,結(jié)果婚禮上竿秆,老公的妹妹穿的比我還像新娘启摄。我一直安慰自己,他們只是感情好幽钢,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布歉备。 她就那樣靜靜地躺著,像睡著了一般匪燕。 火紅的嫁衣襯著肌膚如雪蕾羊。 梳的紋絲不亂的頭發(fā)上喧笔,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音龟再,去河邊找鬼书闸。 笑死,一個胖子當著我的面吹牛利凑,可吹牛的內(nèi)容都是我干的浆劲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哀澈,長吁一口氣:“原來是場噩夢啊……” “哼牌借!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起割按,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤膨报,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哲虾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丙躏,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年束凑,在試婚紗的時候發(fā)現(xiàn)自己被綠了晒旅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡汪诉,死狀恐怖废恋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扒寄,我是刑警寧澤鱼鼓,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站该编,受9級特大地震影響迄本,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜课竣,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一嘉赎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧于樟,春花似錦公条、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春关霸,著一層夾襖步出監(jiān)牢的瞬間传黄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工谒拴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尝江,地道東北人涉波。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓英上,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啤覆。 傳聞我的和親對象是個殘疾皇子苍日,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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

  • 本文主要講了java中多線程的使用方法、線程同步窗声、線程數(shù)據(jù)傳遞相恃、線程狀態(tài)及相應的一些線程函數(shù)用法、概述等笨觅。 首先講...
    李欣陽閱讀 2,454評論 1 15
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,353評論 3 87
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,957評論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的拦耐,為什么轉(zhuǎn)載兩個字加“”呢?因為這絕不是簡單的復制粘貼见剩,我花了五六個小...
    SmartSean閱讀 4,730評論 12 45
  • Java8張圖 11杀糯、字符串不變性 12、equals()方法苍苞、hashCode()方法的區(qū)別 13固翰、...
    Miley_MOJIE閱讀 3,704評論 0 11