欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析Java線程編程中的線程安全與synchronized的使用

 更新時(shí)間:2015年12月03日 08:59:25   作者:海子  
這篇文章主要介紹了Java線程編程中的線程安全與synchronized的使用,synchronized多線程使用時(shí)一定要注意線程之間的沖突問(wèn)題,需要的朋友可以參考下

一.什么時(shí)候會(huì)出現(xiàn)線程安全問(wèn)題?

  在單線程中不會(huì)出現(xiàn)線程安全問(wèn)題,而在多線程編程中,有可能會(huì)出現(xiàn)同時(shí)訪問(wèn)同一個(gè)資源的情況,這種資源可以是各種類型的的資源:一個(gè)變量、一個(gè)對(duì)象、一個(gè)文件、一個(gè)數(shù)據(jù)庫(kù)表等,而當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)資源的時(shí)候,就會(huì)存在一個(gè)問(wèn)題:

  由于每個(gè)線程執(zhí)行的過(guò)程是不可控的,所以很可能導(dǎo)致最終的結(jié)果與實(shí)際上的愿望相違背或者直接導(dǎo)致程序出錯(cuò)。

  舉個(gè)簡(jiǎn)單的例子:

  現(xiàn)在有兩個(gè)線程分別從網(wǎng)絡(luò)上讀取數(shù)據(jù),然后插入一張數(shù)據(jù)庫(kù)表中,要求不能插入重復(fù)的數(shù)據(jù)。

  那么必然在插入數(shù)據(jù)的過(guò)程中存在兩個(gè)操作:

  1)檢查數(shù)據(jù)庫(kù)中是否存在該條數(shù)據(jù);

  2)如果存在,則不插入;如果不存在,則插入到數(shù)據(jù)庫(kù)中。

  假如兩個(gè)線程分別用thread-1和thread-2表示,某一時(shí)刻,thread-1和thread-2都讀取到了數(shù)據(jù)X,那么可能會(huì)發(fā)生這種情況:

  thread-1去檢查數(shù)據(jù)庫(kù)中是否存在數(shù)據(jù)X,然后thread-2也接著去檢查數(shù)據(jù)庫(kù)中是否存在數(shù)據(jù)X。

  結(jié)果兩個(gè)線程檢查的結(jié)果都是數(shù)據(jù)庫(kù)中不存在數(shù)據(jù)X,那么兩個(gè)線程都分別將數(shù)據(jù)X插入數(shù)據(jù)庫(kù)表當(dāng)中。

  這個(gè)就是線程安全問(wèn)題,即多個(gè)線程同時(shí)訪問(wèn)一個(gè)資源時(shí),會(huì)導(dǎo)致程序運(yùn)行結(jié)果并不是想看到的結(jié)果。

  這里面,這個(gè)資源被稱為:臨界資源(也有稱為共享資源)。

  也就是說(shuō),當(dāng)多個(gè)線程同時(shí)訪問(wèn)臨界資源(一個(gè)對(duì)象,對(duì)象中的屬性,一個(gè)文件,一個(gè)數(shù)據(jù)庫(kù)等)時(shí),就可能會(huì)產(chǎn)生線程安全問(wèn)題。

  不過(guò),當(dāng)多個(gè)線程執(zhí)行一個(gè)方法,方法內(nèi)部的局部變量并不是臨界資源,因?yàn)榉椒ㄊ窃跅I蠄?zhí)行的,而Java棧是線程私有的,因此不會(huì)產(chǎn)生線程安全問(wèn)題。

二.如何解決線程安全問(wèn)題?

  那么一般來(lái)說(shuō),是如何解決線程安全問(wèn)題的呢?

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

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

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

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

三.synchronized同步方法或者同步塊

  在了解synchronized關(guān)鍵字的使用方法之前,我們先來(lái)看一個(gè)概念:互斥鎖,顧名思義:能到達(dá)到互斥訪問(wèn)目的的鎖。

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

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

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

  下面通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明synchronized關(guān)鍵字的使用:

  1.synchronized方法

  下面這段代碼中兩個(gè)線程分別調(diào)用insertData對(duì)象插入數(shù)據(jù):

public class Test {
 
  public static void main(String[] args) {
    final InsertData insertData = new InsertData();
     
    new Thread() {
      public void run() {
        insertData.insert(Thread.currentThread());
      };
    }.start();
     
     
    new Thread() {
      public void run() {
        insertData.insert(Thread.currentThread());
      };
    }.start();
  } 
}
 
class InsertData {
  private ArrayList<Integer> arrayList = new ArrayList<Integer>();
   
  public void insert(Thread thread){
    for(int i=0;i<5;i++){
      System.out.println(thread.getName()+"在插入數(shù)據(jù)"+i);
      arrayList.add(i);
    }
  }
}

  此時(shí)程序的輸出結(jié)果為:

201512385248741.jpg (512×235)

說(shuō)明兩個(gè)線程在同時(shí)執(zhí)行insert方法。

  而如果在insert方法前面加上關(guān)鍵字synchronized的話,運(yùn)行結(jié)果為:

class InsertData {
  private ArrayList<Integer> arrayList = new ArrayList<Integer>();
   
  public synchronized void insert(Thread thread){
    for(int i=0;i<5;i++){
      System.out.println(thread.getName()+"在插入數(shù)據(jù)"+i);
      arrayList.add(i);
    }
  }
}

201512385315664.jpg (578×255)

從上輸出結(jié)果說(shuō)明,Thread-1插入數(shù)據(jù)是等Thread-0插入完數(shù)據(jù)之后才進(jìn)行的。說(shuō)明Thread-0和Thread-1是順序執(zhí)行insert方法的。

  這就是synchronized方法。

  不過(guò)有幾點(diǎn)需要注意:

  1)當(dāng)一個(gè)線程正在訪問(wèn)一個(gè)對(duì)象的synchronized方法,那么其他線程不能訪問(wèn)該對(duì)象的其他synchronized方法。這個(gè)原因很簡(jiǎn)單,因?yàn)橐粋€(gè)對(duì)象只有一把鎖,當(dāng)一個(gè)線程獲取了該對(duì)象的鎖之后,其他線程無(wú)法獲取該對(duì)象的鎖,所以無(wú)法訪問(wèn)該對(duì)象的其他synchronized方法。

  2)當(dāng)一個(gè)線程正在訪問(wèn)一個(gè)對(duì)象的synchronized方法,那么其他線程能訪問(wèn)該對(duì)象的非synchronized方法。這個(gè)原因很簡(jiǎn)單,訪問(wèn)非synchronized方法不需要獲得該對(duì)象的鎖,假如一個(gè)方法沒(méi)用synchronized關(guān)鍵字修飾,說(shuō)明它不會(huì)使用到臨界資源,那么其他線程是可以訪問(wèn)這個(gè)方法的,

  3)如果一個(gè)線程A需要訪問(wèn)對(duì)象object1的synchronized方法fun1,另外一個(gè)線程B需要訪問(wèn)對(duì)象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會(huì)產(chǎn)生線程安全問(wèn)題,因?yàn)樗麄冊(cè)L問(wèn)的是不同的對(duì)象,所以不存在互斥問(wèn)題。

  2.synchronized代碼塊

  synchronized代碼塊類似于以下這種形式:

synchronized(synObject) {
        
    }
  當(dāng)在某個(gè)線程中執(zhí)行這段代碼塊,該線程會(huì)獲取對(duì)象synObject的鎖,從而使得其他線程無(wú)法同時(shí)訪問(wèn)該代碼塊。

  synObject可以是this,代表獲取當(dāng)前對(duì)象的鎖,也可以是類中的一個(gè)屬性,代表獲取該屬性的鎖。

  比如上面的insert方法可以改成以下兩種形式:

class InsertData {
  private ArrayList<Integer> arrayList = new ArrayList<Integer>();
   
  public void insert(Thread thread){
    synchronized (this) {
      for(int i=0;i<100;i++){
        System.out.println(thread.getName()+"在插入數(shù)據(jù)"+i);
        arrayList.add(i);
      }
    }
  }
}

class InsertData {
  private ArrayList<Integer> arrayList = new ArrayList<Integer>();
  private Object object = new Object();
   
  public void insert(Thread thread){
    synchronized (object) {
      for(int i=0;i<100;i++){
        System.out.println(thread.getName()+"在插入數(shù)據(jù)"+i);
        arrayList.add(i);
      }
    }
  }
}

  從上面可以看出,synchronized代碼塊使用起來(lái)比synchronized方法要靈活得多。因?yàn)橐苍S一個(gè)方法中只有一部分代碼只需要同步,如果此時(shí)對(duì)整個(gè)方法用synchronized進(jìn)行同步,會(huì)影響程序執(zhí)行效率。而使用synchronized代碼塊就可以避免這個(gè)問(wèn)題,synchronized代碼塊可以實(shí)現(xiàn)只對(duì)需要同步的地方進(jìn)行同步。

  另外,每個(gè)類也會(huì)有一個(gè)鎖,它可以用來(lái)控制對(duì)static數(shù)據(jù)成員的并發(fā)訪問(wèn)。

  并且如果一個(gè)線程執(zhí)行一個(gè)對(duì)象的非static synchronized方法,另外一個(gè)線程需要執(zhí)行這個(gè)對(duì)象所屬類的static synchronized方法,此時(shí)不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問(wèn)static synchronized方法占用的是類鎖,而訪問(wèn)非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完畢");
  }
}

  執(zhí)行結(jié)果;

201512385415343.jpg (465×153)

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

  下面我們看一下synchronized關(guān)鍵字到底做了什么事情,我們來(lái)反編譯它的字節(jié)碼看一下,下面這段代碼反編譯后的字節(jié)碼為:

public class InsertData {
  private Object object = new Object();
   
  public void insert(Thread thread){
    synchronized (object) {
     
    }
  }
   
  public synchronized void insert1(Thread thread){
     
  }
   
  public void insert2(Thread thread){
     
  }
}

201512385447945.jpg (615×538)

 從反編譯獲得的字節(jié)碼可以看出,synchronized代碼塊實(shí)際上多了monitorenter和monitorexit兩條指令。monitorenter指令執(zhí)行時(shí)會(huì)讓對(duì)象的鎖計(jì)數(shù)加1,而monitorexit指令執(zhí)行時(shí)會(huì)讓對(duì)象的鎖計(jì)數(shù)減1,其實(shí)這個(gè)與操作系統(tǒng)里面的PV操作很像,操作系統(tǒng)里面的PV操作就是用來(lái)控制多個(gè)線程對(duì)臨界資源的訪問(wèn)。對(duì)于synchronized方法,執(zhí)行中的線程識(shí)別該方法的 method_info 結(jié)構(gòu)是否有 ACC_SYNCHRONIZED 標(biāo)記設(shè)置,然后它自動(dòng)獲取對(duì)象的鎖,調(diào)用方法,最后釋放鎖。如果有異常發(fā)生,線程自動(dòng)釋放鎖。

有一點(diǎn)要注意:對(duì)于synchronized方法或者synchronized代碼塊,當(dāng)出現(xiàn)異常時(shí),JVM會(huì)自動(dòng)釋放當(dāng)前線程占用的鎖,因此不會(huì)由于異常導(dǎo)致出現(xiàn)死鎖現(xiàn)象。

三.關(guān)于synchronized的其他一些值得注意的地方

1.synchronized與static synchronized 的區(qū)別
  synchronized是對(duì)類的當(dāng)前實(shí)例進(jìn)行加鎖,防止其他線程同時(shí)訪問(wèn)該類的該實(shí)例的所有synchronized塊,注意這里是“類的當(dāng)前實(shí)例”,類的兩個(gè)不同實(shí)例就沒(méi)有這種約束了。那么static synchronized恰好就是要控制類的所有實(shí)例的訪問(wèn)了,static synchronized是限制線程同時(shí)訪問(wèn)jvm中該類的所有實(shí)例同時(shí)訪問(wèn)對(duì)應(yīng)的代碼快。實(shí)際上,在類中某方法或某代碼塊中有synchronized,那么在生成一個(gè)該類實(shí)例后,該類也就有一個(gè)監(jiān)視快,放置線程并發(fā)訪問(wèn)改實(shí)例synchronized保護(hù)快,而static synchronized則是所有該類的實(shí)例公用一個(gè)監(jiān)視快了,也就是兩個(gè)的區(qū)別了,也就是synchronized相當(dāng)于this.synchronized,而
  static synchronized相當(dāng)于Something.synchronized.
  一個(gè)日本作者-結(jié)成浩的《java多線程設(shè)計(jì)模式》有這樣的一個(gè)列子:
  

pulbic class Something(){
  public synchronized void isSyncA(){}
  public synchronized void isSyncB(){}
  public static synchronized void cSyncA(){}
  public static synchronized void cSyncB(){}
  }

  那么,加入有Something類的兩個(gè)實(shí)例a與b,那么下列組方法何以被1個(gè)以上線程同時(shí)訪問(wèn)呢
  a.   x.isSyncA()與x.isSyncB()
  b.   x.isSyncA()與y.isSyncA()
  c.   x.cSyncA()與y.cSyncB()
  d.   x.isSyncA()與Something.cSyncA()
  這里,很清楚的可以判斷:
  a,都是對(duì)同一個(gè)實(shí)例的synchronized域訪問(wèn),因此不能被同時(shí)訪問(wèn)
  b,是針對(duì)不同實(shí)例的,因此可以同時(shí)被訪問(wèn)
  c,因?yàn)槭莝tatic synchronized,所以不同實(shí)例之間仍然會(huì)被限制,相當(dāng)于Something.isSyncA()與   Something.isSyncB()了,因此不能被同時(shí)訪問(wèn)。
  那么,第d呢?,書上的 答案是可以被同時(shí)訪問(wèn)的,答案理由是synchronzied的是實(shí)例方法與synchronzied的類方法由于鎖定(lock)不同的原因。
  個(gè)人分析也就是synchronized 與static synchronized 相當(dāng)于兩幫派,各自管各自,相互之間就無(wú)約束了,可以被同時(shí)訪問(wèn)。目前還不是分清楚java內(nèi)部設(shè)計(jì)synchronzied是怎么樣實(shí)現(xiàn)的。
  結(jié)論:A: synchronized static是某個(gè)類的范圍,synchronized static cSync{}防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)    類中的synchronized static 方法。它可以對(duì)類的所有對(duì)象實(shí)例起作用。
  B: synchronized 是某實(shí)例的范圍,synchronized isSync(){}防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)實(shí)例中的synchronized 方法。
  2.synchronized方法與synchronized代碼快的區(qū)別
  synchronized methods(){} 與synchronized(this){}之間沒(méi)有什么區(qū)別,只是 synchronized methods(){} 便于閱讀理解,而synchronized(this){}可以更精確的控制沖突限制訪問(wèn)區(qū)域,有時(shí)候表現(xiàn)更高效率。
  3.synchronized關(guān)鍵字是不能繼承的

相關(guān)文章

  • Spring配置文件中parent與abstract的使用

    Spring配置文件中parent與abstract的使用

    這篇文章主要介紹了Spring配置文件中parent與abstract的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java 實(shí)現(xiàn)web項(xiàng)目啟動(dòng)加載properties屬性文件

    java 實(shí)現(xiàn)web項(xiàng)目啟動(dòng)加載properties屬性文件

    這篇文章主要介紹了java 實(shí)現(xiàn)web項(xiàng)目啟動(dòng)加載properties屬性文件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它

    Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它

    這篇文章主要介紹了Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它,需要的朋友可以參考下
    2017-02-02
  • Spring Data JDBC介紹及實(shí)現(xiàn)代碼

    Spring Data JDBC介紹及實(shí)現(xiàn)代碼

    這篇文章主要介紹了Spring Data JDBC介紹及實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • java小數(shù)位的例子

    java小數(shù)位的例子

    在java中要保留數(shù)字小數(shù)位我們有常用的四種方法,分別為:四舍五入,DecimalFormat,format,String .format與struts標(biāo)簽操作實(shí)現(xiàn),下面給出例子
    2013-11-11
  • Java利用for循環(huán)打印菱形的實(shí)例教程

    Java利用for循環(huán)打印菱形的實(shí)例教程

    這篇文章主要給大家介紹了關(guān)于Java利用for循環(huán)打印菱形的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Java數(shù)據(jù)結(jié)構(gòu)-HashMap詳解

    Java數(shù)據(jù)結(jié)構(gòu)-HashMap詳解

    這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)-HashMap,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 將SpringBoot的Jar注冊(cè)成Windows服務(wù)的實(shí)現(xiàn)方法

    將SpringBoot的Jar注冊(cè)成Windows服務(wù)的實(shí)現(xiàn)方法

    當(dāng)前項(xiàng)目有個(gè)地圖編輯器,后端用的是SpringBoot框架,外網(wǎng)剛好有一臺(tái)空閑的Windows服務(wù)器就直接拿來(lái)用了,將Java程序部署成Windows服務(wù)可以用WinSW (Windows Service Wrapper)來(lái)實(shí)現(xiàn),文中有詳細(xì)的操作步驟,需要的朋友可以參考下
    2023-11-11
  • java反射耗時(shí)測(cè)試案例解析

    java反射耗時(shí)測(cè)試案例解析

    這篇文章主要介紹了java反射耗時(shí)測(cè)試案例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • 詳解Java分布式系統(tǒng)中一致性哈希算法

    詳解Java分布式系統(tǒng)中一致性哈希算法

    這篇文章主要介紹了Java分布式系統(tǒng)中一致性哈希算法,對(duì)算法感興趣的同學(xué),可以參考下
    2021-04-04

最新評(píng)論