Java多線程干貨系列—(二)synchronized

前言

本篇主要介紹Java多線程中的同步,也就是如何在Java語言中寫出線程安全的程序,如何在Java語言中解決非線程安全的相關(guān)問題。沒錯(cuò)就是使用synchronized。

正文

如何解決線程安全問題?

那么一般來說,是如何解決線程安全問題的呢?

基本上所有的并發(fā)模式在解決線程安全問題時(shí),都采用“序列化訪問臨界資源”的方案,即在同一時(shí)刻,只能有一個(gè)線程訪問臨界資源,也稱作同步互斥訪問。

通常來說,是在訪問臨界資源的代碼前面加上一個(gè)鎖,當(dāng)訪問完臨界資源后釋放鎖,讓其他線程繼續(xù)訪問。

在Java中,提供了兩種方式來實(shí)現(xiàn)同步互斥訪問:synchronized和Lock。

本文主要講述synchronized的使用方法,Lock的使用方法在下一篇博文中講述。

synchronized同步方法

synchronized是Java語言的關(guān)鍵字,當(dāng)它用來修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼。在了解synchronized關(guān)鍵字的使用方法之前,我們先來看一個(gè)概念:互斥鎖,顧名思義:能到達(dá)到互斥訪問目的的鎖。

舉個(gè)簡(jiǎn)單的例子:如果對(duì)臨界資源加上互斥鎖,當(dāng)一個(gè)線程在訪問該臨界資源時(shí),其他線程便只能等待。

在Java中,每一個(gè)對(duì)象都擁有一個(gè)鎖標(biāo)記(monitor),也稱為監(jiān)視器,多線程同時(shí)訪問某個(gè)對(duì)象時(shí),線程只有獲取了該對(duì)象的鎖才能訪問。

在Java中,可以使用synchronized關(guān)鍵字來標(biāo)記一個(gè)方法或者代碼塊,當(dāng)某個(gè)線程調(diào)用該對(duì)象的synchronized方法或者訪問synchronized代碼塊時(shí),這個(gè)線程便獲得了該對(duì)象的鎖,其他線程暫時(shí)無法訪問這個(gè)方法,只有等待這個(gè)方法執(zhí)行完畢或者代碼塊執(zhí)行完畢,這個(gè)線程才會(huì)釋放該對(duì)象的鎖,其他線程才能執(zhí)行這個(gè)方法或者代碼塊。

synchronized的使用

  • synchronized代碼塊,被修飾的代碼成為同步語句塊,其作用的范圍是調(diào)用這個(gè)代碼塊的對(duì)象,我們?cè)谟胹ynchronized關(guān)鍵字的時(shí)候,能縮小代碼段的范圍就盡量縮小,能在代碼段上加同步就不要再整個(gè)方法上加同步。這叫減小鎖的粒度,使代碼更大程度的并發(fā)。

  • synchronized方法,被修飾的方法成為同步方法,其作用范圍是整個(gè)方法,作用對(duì)象是調(diào)用這個(gè)方法的對(duì)象。

  • synchronized靜態(tài)方法,修飾一個(gè)static靜態(tài)方法,其作用范圍是整個(gè)靜態(tài)方法,作用對(duì)象是這個(gè)類的所有對(duì)象。

  • synchronized類,其作用范圍是Synchronized后面括號(hào)括起來的部分synchronized(className.class),作用的對(duì)象是這個(gè)類的所有對(duì)象。

  • synchronized(),()中是鎖住的對(duì)象, synchronized(this)鎖住的只是對(duì)象本身,同一個(gè)類的不同對(duì)象調(diào)用的synchronized方法并不會(huì)被鎖住,而synchronized(className.class)實(shí)現(xiàn)了全局鎖的功能,所有這個(gè)類的對(duì)象調(diào)用這個(gè)方法都受到鎖的影響,此外()中還可以添加一個(gè)具體的對(duì)象,實(shí)現(xiàn)給具體對(duì)象加鎖。

synchronized (object) {
  //在同步代碼塊中對(duì)對(duì)象進(jìn)行操作
}

synchronized注意事項(xiàng)

  • 當(dāng)兩個(gè)并發(fā)線程訪問同一個(gè)對(duì)象中的synchronized代碼塊時(shí),在同一時(shí)刻只能有一個(gè)線程得到執(zhí)行,另一個(gè)線程受阻塞,必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。兩個(gè)線程間是互斥的,因?yàn)樵趫?zhí)行synchronized代碼塊時(shí)會(huì)鎖定當(dāng)前的對(duì)象,只有執(zhí)行完該代碼塊才能釋放該對(duì)象鎖,下一個(gè)線程才能執(zhí)行并鎖定該對(duì)象。

  • 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。(兩個(gè)線程使用的是同一個(gè)對(duì)象)

  • 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞(同上,兩個(gè)線程使用的是同一個(gè)對(duì)象)。

下面通過代碼來實(shí)現(xiàn):

1)當(dāng)兩個(gè)并發(fā)線程訪問同一個(gè)對(duì)象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

package ths;

public class Thread1 implements Runnable {  
     public void run() {  
          synchronized(this) {  
               for (int i = 0; i < 5; i++) {  
                    System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);  
               }  
          }  
     }  
     public static void main(String[] args) {  
          Thread1 t1 = new Thread1();  
          Thread ta = new Thread(t1, "A");  
          Thread tb = new Thread(t1, "B");  
          ta.start();  
          tb.start();  
     } 
}

輸出結(jié)果:

A synchronized loop 0  
A synchronized loop 1  
A synchronized loop 2  
A synchronized loop 3  
A synchronized loop 4  
B synchronized loop 0  
B synchronized loop 1  
B synchronized loop 2  
B synchronized loop 3  
B synchronized loop 4

2)然而,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

package ths;

public class Thread2 {  
     public void m4t1() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }  
     }  
     public void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }  
     public static void main(String[] args) {  
          final Thread2 myt2 = new Thread2();  
          Thread t1 = new Thread(  new Runnable() {  public void run() {  myt2.m4t1();  }  }, "t1"  );  
          Thread t2 = new Thread(  new Runnable() {  public void run() { myt2.m4t2();   }  }, "t2"  );  
          t1.start();  
          t2.start();  
     } 
}

輸出結(jié)果:

t1 : 4  
t2 : 4  
t1 : 3  
t2 : 3  
t1 : 2  
t2 : 2  
t1 : 1  
t2 : 1  
t1 : 0  
t2 : 0

3)尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

//修改Thread2.m4t2()方法:  
     public void m4t2() {  
          synchronized(this) {  
               int i = 5;  
               while( i-- > 0) {  
                    System.out.println(Thread.currentThread().getName() + " : " + i);  
                    try {  
                         Thread.sleep(500);  
                    } catch (InterruptedException ie) {  
                    }  
               }  
          }

     }

輸出結(jié)果:

t1 : 4  
t1 : 3  
t1 : 2  
t1 : 1  
t1 : 0  
t2 : 4  
t2 : 3  
t2 : 2  
t2 : 1  
t2 : 0

4)第三個(gè)例子同樣適用其它同步代碼塊。也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對(duì)象鎖。結(jié)果,其它線程對(duì)該object對(duì)象所有同步代碼部分的訪問都被暫時(shí)阻塞。

 //修改Thread2.m4t2()方法如下:

     public synchronized void m4t2() {  
          int i = 5;  
          while( i-- > 0) {  
               System.out.println(Thread.currentThread().getName() + " : " + i);  
               try {  
                    Thread.sleep(500);  
               } catch (InterruptedException ie) {  
               }  
          }  
     }

輸出結(jié)果:

t1 : 4  
t1 : 3  
t1 : 2  
t1 : 1  
t1 : 0  
t2 : 4  
t2 : 3  
t2 : 2  
t2 : 1  
t2 : 0

5)每個(gè)類也會(huì)有一個(gè)鎖,它可以用來控制對(duì)static數(shù)據(jù)成員的并發(fā)訪問。
并且如果一個(gè)線程執(zhí)行一個(gè)對(duì)象的非static synchronized方法,另外一個(gè)線程需要執(zhí)行這個(gè)對(duì)象所屬類的static synchronized方法,此時(shí)不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問static synchronized方法占用的是類鎖,而訪問非static synchronized方法占用的是對(duì)象鎖,所以不存在互斥現(xiàn)象。
代碼如下:

public class Test {
 
    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread(){
            @Override
            public void run() {
                insertData.insert();
            }
        }.start(); 
        new Thread(){
            @Override
            public void run() {
                insertData.insert1();
            }
        }.start();
    }  
}
 
class InsertData { 
    public synchronized void insert(){
        System.out.println("執(zhí)行insert");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執(zhí)行insert完畢");
    }
     
    public synchronized static void insert1() {
        System.out.println("執(zhí)行insert1");
        System.out.println("執(zhí)行insert1完畢");
    }
}

輸出結(jié)果:

執(zhí)行insert
執(zhí)行insert1
執(zhí)行insert1完畢
執(zhí)行insert完畢

第一個(gè)線程里面執(zhí)行的是insert方法,不會(huì)導(dǎo)致第二個(gè)線程執(zhí)行insert1方法發(fā)生阻塞現(xiàn)象。

面試題

當(dāng)一個(gè)線程進(jìn)入一個(gè)對(duì)象的synchronized方法A之后,其它線程是否可進(jìn)入此對(duì)象的synchronized方法B?
答:不能。其它線程只能訪問該對(duì)象的非同步方法,同步方法則不能進(jìn)入。因?yàn)榉庆o態(tài)方法上的synchronized修飾符要求執(zhí)行方法時(shí)要獲得對(duì)象的鎖,如果已經(jīng)進(jìn)入A方法說明對(duì)象鎖已經(jīng)被取走,那么試圖進(jìn)入B方法的線程就只能在等鎖池(注意不是等待池哦)中等待對(duì)象的鎖。

synchronized關(guān)鍵字的用法?
答:synchronized關(guān)鍵字可以將對(duì)象或者方法標(biāo)記為同步,以實(shí)現(xiàn)對(duì)對(duì)象和方法的互斥訪問,可以用synchronized(對(duì)象) { … }定義同步代碼塊,或者在聲明方法時(shí)將synchronized作為方法的修飾符。

簡(jiǎn)述synchronized 和java.util.concurrent.locks.Lock的異同?
答:Lock是Java 5以后引入的新的API,和關(guān)鍵字synchronized相比主要相同點(diǎn):Lock 能完成synchronized所實(shí)現(xiàn)的所有功能;主要不同點(diǎn):Lock有比synchronized更精確的線程語義和更好的性能,而且不強(qiáng)制性的要求一定要獲得鎖。synchronized會(huì)自動(dòng)釋放鎖,而Lock一定要求程序員手工釋放,并且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)

總結(jié)

以上就是synchronized的概念和基本使用用法,下一篇博文中將介紹Lock,希望對(duì)你有所幫助。


一直覺得自己寫的不是技術(shù),而是情懷,一篇篇文章是自己這一路走來的痕跡??繉I(yè)技能的成功是最具可復(fù)制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識(shí)的蒙塵,希望我能幫你理清知識(shí)的脈絡(luò),希望未來技術(shù)之巔上有你也有我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容