Synchronized到底鎖住的是誰?

先來一道并發(fā)編程筆試題

成都創(chuàng)新互聯(lián)公司專注于企業(yè)全網(wǎng)營銷推廣、網(wǎng)站重做改版、陸河網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5頁面制作、商城建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為陸河等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

題目:利用5個(gè)線程并發(fā)執(zhí)行,num數(shù)字累計(jì)計(jì)數(shù)到10000,并打印。

/**
* Description:
* 利用5個(gè)線程并發(fā)執(zhí)行,num數(shù)字累加計(jì)數(shù)到10000,并打印。
* 2019-06-13
* Created with OKevin.
*/
public class Count {
   private int num = 0;

   public static void main(String[] args) throws InterruptedException {
       Count count = new Count();

       Thread thread1 = new Thread(count.new MyThread());
       Thread thread2 = new Thread(count.new MyThread());
       Thread thread3 = new Thread(count.new MyThread());
       Thread thread4 = new Thread(count.new MyThread());
       Thread thread5 = new Thread(count.new MyThread());
       thread1.start();
       thread2.start();
       thread3.start();
       thread4.start();
       thread5.start();
       thread1.join();
       thread2.join();
       thread3.join();
       thread4.join();
       thread5.join();

       System.out.println(count.num);

   }

   private synchronized void increse() {
       for (int i = 0; i < 2000; i++) {
           num++;
       }
   }

   class MyThread implements Runnable {
       @Override
       public void run() {
           increse();
       }
   }
}

這道并發(fā)編程面試題,題目不難,方法簡單。其中涉及一個(gè)核心知識(shí)點(diǎn)——synchronized(當(dāng)然這題的解法有很多),這也是本文想要弄清的主題。

synchronized被大大小小的程序員廣泛使用,有的程序員偷懶,在要求保證線程安全時(shí),不加思索的就在方法前加入了synchronized關(guān)鍵字(例如我剛才那道大題)。偷懶歸偷懶,CodeReview總是要進(jìn)行的,面對(duì)同事的“指責(zé)”,要求優(yōu)化這個(gè)方法,將synchronized使用同步代碼塊的方式提高效率。

Synchronized到底鎖住的是誰?

synchronized要按照同步代碼塊來保證線程安全,這可就加在方法“復(fù)雜”多了。有:synchronized(this){}這么寫的,也有synchronized(Count.class){}這么寫的,還有定義了一個(gè)private Object obj = new Object; ….synchronized(obj){}這么寫的。此時(shí)不禁在心里“W*F”。

synchronized你到底鎖住的是誰?

synchronized從語法的維度一共有3個(gè)用法:

    1. 靜態(tài)方法加上關(guān)鍵字
    1. 實(shí)例方法(也就是普通方法)加上關(guān)鍵字
    1. 方法中使用同步代碼塊

前兩種方式最為偷懶,第三種方式比前兩種性能要好。

synchronized從鎖的是誰的維度一共有兩種情況:

    1. 鎖住類
    1. 鎖住對(duì)象實(shí)例

我們還是從直觀的語法結(jié)構(gòu)上來講述synchronized。

1)靜態(tài)方法上的鎖

靜態(tài)方法是屬于“類”,不屬于某個(gè)實(shí)例,是所有對(duì)象實(shí)例所共享的方法。也就是說如果在靜態(tài)方法上加入synchronized,那么它獲取的就是這個(gè)類的鎖,鎖住的就是這個(gè)類

2)實(shí)例方法(普通方法)上的鎖

實(shí)例方法并不是類所獨(dú)有的,每個(gè)對(duì)象實(shí)例獨(dú)立擁有它,它并不被對(duì)象實(shí)例所共享。這也比較能推出,在實(shí)例方法上加入synchronized,那么它獲取的就是這個(gè)累的鎖,鎖住的就是這個(gè)對(duì)象實(shí)例

那鎖住類還是鎖住對(duì)象實(shí)例,這跟我線程安全關(guān)系大嗎?大,差之毫厘謬以千里的大。為了更好的理解鎖住類還是鎖住對(duì)象實(shí)例,在進(jìn)入“3)方法中使用同步代碼塊”前,先直觀的感受下這兩者的區(qū)別。

對(duì)實(shí)例方法(普通方法)上加關(guān)鍵字鎖住對(duì)象實(shí)例鎖的解釋

首先定義一個(gè)Demo類,其中的實(shí)例方法加上了synchronized關(guān)鍵字,按照所述也就是說鎖住的對(duì)象實(shí)例。

/**
* Description:
* 死循環(huán),目的是兩個(gè)線程搶占一個(gè)鎖時(shí),只要其中一個(gè)線程獲取,另一個(gè)線程就會(huì)一直阻塞
* 2019-06-13
* Created with OKevin.
*/
public class Demo {

   public synchronized void demo() {
       while (true) {   //synchronized方法內(nèi)部是一個(gè)死循環(huán),一旦一個(gè)線程持有過后就不會(huì)釋放這個(gè)鎖
           System.out.println(Thread.currentThread());
       }
   }
}

可以看到在demo方法中定義了一個(gè)死循環(huán),一旦一個(gè)線程持有這個(gè)鎖后其他線程就不可能獲取這個(gè)鎖。結(jié)合上述synchronized修飾實(shí)例方法鎖住的是對(duì)象實(shí)例,如果兩個(gè)線程針對(duì)的是一個(gè)對(duì)象實(shí)例,那么其中一個(gè)線程必然不可能獲取這個(gè)鎖;如果兩個(gè)線程針對(duì)的是兩個(gè)對(duì)象實(shí)例,那么這兩個(gè)線程不相關(guān)均能獲取這個(gè)鎖。

自定義線程,調(diào)用demo方法。

/**
* Description:
* 自定義線程
* 2019-06-13
* Created with OKevin.
*/
public class MyThread implements Runnable {
   private Demo demo;

   public MyThread(Demo demo) {
       this.demo = demo;
   }

   @Override
   public void run() {
       demo.demo();
   }
}

測試程序1:兩個(gè)線程搶占一個(gè)對(duì)象實(shí)例的鎖

/**
* Description:
* 兩個(gè)線程搶占一個(gè)對(duì)象實(shí)例的鎖
* 2019-06-13
* Created with OKevin.
*/
public class Main1 {
   public static void main(String[] args) {
       Demo demo = new Demo();
       Thread thread1 = new Thread(new MyThread(demo));
       Thread thread2 = new Thread(new MyThread(demo));
       thread1.start();
       thread2.start();
   }
}

Synchronized到底鎖住的是誰?

?如上圖所示,輸出結(jié)果顯然只會(huì)打印一個(gè)線程的信息,另一個(gè)線程永遠(yuǎn)也獲取不到這個(gè)鎖。

測試程序2:兩個(gè)線程分別搶占兩個(gè)對(duì)象實(shí)例的鎖

/**
* Description:
* 兩個(gè)線程分別搶占兩個(gè)對(duì)象實(shí)例的鎖
* 2019-06-13
* Created with OKevin.
*/
public class Main2 {
   public static void main(String[] args) {
       Demo demo1 = new Demo();
       Demo demo2 = new Demo();
       Thread thread1 = new Thread(new MyThread(demo1));
       Thread thread2 = new Thread(new MyThread(demo2));
       thread1.start();
       thread2.start();
   }
}

Synchronized到底鎖住的是誰?

如上圖所示,顯然,兩個(gè)線程均進(jìn)入到了demo方法,也就是均獲取到了鎖,證明,兩個(gè)線程搶占的就不是同一個(gè)鎖,這就是synchronized修飾實(shí)例方法時(shí),鎖住的是對(duì)象實(shí)例的解釋。

對(duì)靜態(tài)方法上加關(guān)鍵字鎖住類鎖的解釋

靜態(tài)方法是類所有對(duì)象實(shí)例所共享的,無論定義多少個(gè)實(shí)例,是要是靜態(tài)方法上的鎖,它至始至終只有1個(gè)。將上面的程序Demo中的方法加上static,無論使用“測試程序1”還是“測試程序2”,均只有一個(gè)線程可以搶占到鎖,另一個(gè)線程仍然是永遠(yuǎn)無法獲取到鎖。

讓我們重新回到從語法結(jié)構(gòu)上解釋synchronized。

3)方法中使用同步代碼塊

程序的改良優(yōu)化需要建立在有堅(jiān)實(shí)的基礎(chǔ),如果在不了解其內(nèi)部機(jī)制,改良也僅僅是“形式主義”。

結(jié)合開始CodeReview的例子:

你的同事在CodeReview時(shí),要求你將實(shí)例方法上的synchronized,改為效率更高的同步代碼塊方式。在你不清楚同步代碼的用法時(shí),網(wǎng)上搜到了一段synchronized(this){}代碼,復(fù)制下來發(fā)現(xiàn)也能用,此時(shí)你以為你改良優(yōu)化了代碼。但實(shí)際上,你可能只是做了一點(diǎn)形式主義上的優(yōu)化。

為什么這么說?這需要清楚地認(rèn)識(shí)同步代碼塊到底應(yīng)該怎么用。

3.1)synchronized(this){...}

this關(guān)鍵字所代表的意思是該對(duì)象實(shí)例,換句話說,這種用法synchronized鎖住的仍然是對(duì)象實(shí)例,他和public synchronized void demo(){}可以說僅僅是做了語法上的改變。?

/**
* 2019-06-13
* Created with OKevin.
**/
public class Demo {

   public synchronized void demo1() {
       while (true) {  //死循環(huán)目的是為了讓線程一直持有該鎖
           System.out.println(Thread.currentThread());
       }
   }

   public synchronized void demo2() {
       while (true) {
           System.out.println(Thread.currentThread());
       }
   }
}

改為以下方式:?

/**
* Description:
* synchronized同步代碼塊對(duì)本實(shí)例加鎖(this)
* 假設(shè)demo1與demo2方法不相關(guān),此時(shí)兩個(gè)線程對(duì)同一個(gè)對(duì)象實(shí)例分別調(diào)用demo1與demo2,只要其中一個(gè)線程獲取到了鎖即執(zhí)行了demo1或者demo2,此時(shí)另一個(gè)線程會(huì)永遠(yuǎn)處于阻塞狀態(tài)
* 2019-06-13
* Created with OKevin.
*/
public class Demo {

   public void demo1() {
       synchronized (this) {
           while (true) {  //死循環(huán)目的是為了讓線程一直持有該鎖
               System.out.println(Thread.currentThread());
           }
       }
   }

   public void demo2() {
       synchronized (this) {
           while (true) {
               System.out.println(Thread.currentThread());
           }
       }
   }
}

也許后者在JVM中可能會(huì)做一些特殊的優(yōu)化,但從代碼分析上來講,兩者并沒有做到很大的優(yōu)化,線程1執(zhí)行demo1,線程2執(zhí)行demo2,由于兩個(gè)方法均是搶占對(duì)象實(shí)例的鎖,只要有一個(gè)線程獲取到鎖,另外一個(gè)線程只能阻塞等待,即使兩個(gè)方法不相關(guān)。

3.2)private Object obj = new Object(); ???synchronized(obj){...}
/**
* Description:
* synchronized同步代碼塊對(duì)對(duì)象內(nèi)部的實(shí)例加鎖
* 假設(shè)demo1與demo2方法不相關(guān),此時(shí)兩個(gè)線程對(duì)同一個(gè)對(duì)象實(shí)例分別調(diào)用demo1與demo2,均能獲取各自的鎖
* 2019-06-13
* Created with OKevin.
*/
public class Demo {
   private Object lock1 = new Object();
   private Object lock2 = new Object();

   public void demo1() {
       synchronized (lock1) {
           while (true) {  //死循環(huán)目的是為了讓線程一直持有該鎖
               System.out.println(Thread.currentThread());
           }
       }
   }

   public void demo2() {
       synchronized (lock2) {
           while (true) {
               System.out.println(Thread.currentThread());
           }
       }
   }
}

經(jīng)過上面的分析,看到這里,你可能會(huì)開始懂了,可以看到demo1方法中的同步代碼塊鎖住的是lock1對(duì)象實(shí)例,demo2方法中的同步代碼塊鎖住的是lock2對(duì)象實(shí)例。如果線程1執(zhí)行demo1,線程2執(zhí)行demo2,由于兩個(gè)方法搶占的是不同的對(duì)象實(shí)例鎖,也就是說兩個(gè)線程均能獲取到鎖執(zhí)行各自的方法(當(dāng)然前提是兩個(gè)方法互不相關(guān),才不會(huì)出現(xiàn)邏輯錯(cuò)誤)。

3.3)synchronized(Demo.class){...}

這種形式等同于搶占獲取類鎖,這種方式,同樣和3.1一樣,收效甚微。

所以CodeReivew后的代碼應(yīng)該是3.2) private Object obj = new Object(); ???synchronized(obj){...},這才是對(duì)你代碼的改良優(yōu)化。


本文的重點(diǎn)是你有沒有收獲與成長,其余的都不重要,希望讀者們能謹(jǐn)記這一點(diǎn)。同時(shí)我經(jīng)過多年的收藏目前也算收集到了一套完整的學(xué)習(xí)資料,包括但不限于:分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、Jvm性能調(diào)優(yōu)、Spring,MyBatis,Nginx源碼分析,redis,ActiveMQ、、Mycat、Netty、Kafka、MySQL、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多個(gè)知識(shí)點(diǎn)高級(jí)進(jìn)階干貨,希望對(duì)想成為架構(gòu)師的朋友有一定的參考和幫助

需要以下資料的可以加一下三千人Java技術(shù)交流分享群:“708 701 457”免費(fèi)獲取

Synchronized到底鎖住的是誰?
Synchronized到底鎖住的是誰?

標(biāo)題名稱:Synchronized到底鎖住的是誰?
網(wǎng)頁路徑:http://muchs.cn/article26/pioicg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、網(wǎng)站導(dǎo)航、靜態(tài)網(wǎng)站、服務(wù)器托管外貿(mào)網(wǎng)站建設(shè)、標(biāo)簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)