<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問(wèn) http://qaseven.github.io/

    Java并發(fā)編程:synchronized

    雖然多線(xiàn)程編程極大地提高了效率,但是也會(huì)帶來(lái)一定的隱患。比如說(shuō)兩個(gè)線(xiàn)程同時(shí)往一個(gè)數(shù)據(jù)庫(kù)表中插入不重復(fù)的數(shù)據(jù),就可能會(huì)導(dǎo)致數(shù)據(jù)庫(kù)中插入了相同的數(shù)據(jù)。今天我們就來(lái)一起討論下線(xiàn)程安全問(wèn)題,以及Java中提供了什么機(jī)制來(lái)解決線(xiàn)程安全問(wèn)題。
      一.什么時(shí)候會(huì)出現(xiàn)線(xiàn)程安全問(wèn)題?
      在單線(xiàn)程中不會(huì)出現(xiàn)線(xiàn)程安全問(wèn)題,而在多線(xiàn)程編程中,有可能會(huì)出現(xiàn)同時(shí)訪問(wèn)同一個(gè)資源的情況,這種資源可以是各種類(lèi)型的的資源:一個(gè)變量、一個(gè)對(duì)象、一個(gè)文件、一個(gè)數(shù)據(jù)庫(kù)表等,而當(dāng)多個(gè)線(xiàn)程同時(shí)訪問(wèn)同一個(gè)資源的時(shí)候,就會(huì)存在一個(gè)問(wèn)題:
      由于每個(gè)線(xiàn)程執(zhí)行的過(guò)程是不可控的,所以很可能導(dǎo)致最終的結(jié)果與實(shí)際上的愿望相違背或者直接導(dǎo)致程序出錯(cuò)。
      舉個(gè)簡(jiǎn)單的例子:
      現(xiàn)在有兩個(gè)線(xiàn)程分別從網(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è)線(xiàn)程分別用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è)線(xiàn)程檢查的結(jié)果都是數(shù)據(jù)庫(kù)中不存在數(shù)據(jù)X,那么兩個(gè)線(xiàn)程都分別將數(shù)據(jù)X插入數(shù)據(jù)庫(kù)表當(dāng)中。
      這個(gè)就是線(xiàn)程安全問(wèn)題,即多個(gè)線(xiàn)程同時(shí)訪問(wèn)一個(gè)資源時(shí),會(huì)導(dǎo)致程序運(yùn)行結(jié)果并不是想看到的結(jié)果。
      這里面,這個(gè)資源被稱(chēng)為:臨界資源(也有稱(chēng)為共享資源)。
      也就是說(shuō),當(dāng)多個(gè)線(xiàn)程同時(shí)訪問(wèn)臨界資源(一個(gè)對(duì)象,對(duì)象中的屬性,一個(gè)文件,一個(gè)數(shù)據(jù)庫(kù)等)時(shí),就可能會(huì)產(chǎn)生線(xiàn)程安全問(wèn)題。
      不過(guò),當(dāng)多個(gè)線(xiàn)程執(zhí)行一個(gè)方法,方法內(nèi)部的局部變量并不是臨界資源,因?yàn)榉椒ㄊ窃跅I蠄?zhí)行的,而Java棧是線(xiàn)程私有的,因此不會(huì)產(chǎn)生線(xiàn)程安全問(wèn)題。
      二.如何解決線(xiàn)程安全問(wèn)題?
      那么一般來(lái)說(shuō),是如何解決線(xiàn)程安全問(wèn)題的呢?
      基本上所有的并發(fā)模式在解決線(xiàn)程安全問(wèn)題時(shí),都采用“序列化訪問(wèn)臨界資源”的方案,即在同一時(shí)刻,只能有一個(gè)線(xiàn)程訪問(wèn)臨界資源,也稱(chēng)作同步互斥訪問(wèn)。
      通常來(lái)說(shuō),是在訪問(wèn)臨界資源的代碼前面加上一個(gè)鎖,當(dāng)訪問(wèn)完臨界資源后釋放鎖,讓其他線(xià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è)線(xiàn)程在訪問(wèn)該臨界資源時(shí),其他線(xiàn)程便只能等待。
      在Java中,每一個(gè)對(duì)象都擁有一個(gè)鎖標(biāo)記(monitor),也稱(chēng)為監(jiān)視器,多線(xiàn)程同時(shí)訪問(wèn)某個(gè)對(duì)象時(shí),線(xiàn)程只有獲取了該對(duì)象的鎖才能訪問(wèn)。
      在Java中,可以使用synchronized關(guān)鍵字來(lái)標(biāo)記一個(gè)方法或者代碼塊,當(dāng)某個(gè)線(xiàn)程調(diào)用該對(duì)象的synchronized方法或者訪問(wèn)synchronized代碼塊時(shí),這個(gè)線(xiàn)程便獲得了該對(duì)象的鎖,其他線(xiàn)程暫時(shí)無(wú)法訪問(wèn)這個(gè)方法,只有等待這個(gè)方法執(zhí)行完畢或者代碼塊執(zhí)行完畢,這個(gè)線(xiàn)程才會(huì)釋放該對(duì)象的鎖,其他線(xiàn)程才能執(zhí)行這個(gè)方法或者代碼塊。
      下面通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明synchronized關(guān)鍵字的使用:
      1.synchronized方法
      下面這段代碼中兩個(gè)線(xiàn)程分別調(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é)果為:
      說(shuō)明兩個(gè)線(xiàn)程在同時(shí)執(zhí)行insert方法。
      而如果在insert方法前面加上關(guān)鍵字synchronized的話(huà),運(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);
    }
    }
    }
      從上輸出結(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è)線(xiàn)程正在訪問(wèn)一個(gè)對(duì)象的synchronized方法,那么其他線(xiàn)程不能訪問(wèn)該對(duì)象的其他synchronized方法。這個(gè)原因很簡(jiǎn)單,因?yàn)橐粋€(gè)對(duì)象只有一把鎖,當(dāng)一個(gè)線(xiàn)程獲取了該對(duì)象的鎖之后,其他線(xiàn)程無(wú)法獲取該對(duì)象的鎖,所以無(wú)法訪問(wèn)該對(duì)象的其他synchronized方法。
      2)當(dāng)一個(gè)線(xiàn)程正在訪問(wèn)一個(gè)對(duì)象的synchronized方法,那么其他線(xiàn)程能訪問(wèn)該對(duì)象的非synchronized方法。這個(gè)原因很簡(jiǎn)單,訪問(wèn)非synchronized方法不需要獲得該對(duì)象的鎖,假如一個(gè)方法沒(méi)用synchronized關(guān)鍵字修飾,說(shuō)明它不會(huì)使用到臨界資源,那么其他線(xiàn)程是可以訪問(wèn)這個(gè)方法的,
      3)如果一個(gè)線(xiàn)程A需要訪問(wèn)對(duì)象object1的synchronized方法fun1,另外一個(gè)線(xiàn)程B需要訪問(wèn)對(duì)象object2的synchronized方法fun1,即使object1和object2是同一類(lèi)型),也不會(huì)產(chǎn)生線(xiàn)程安全問(wèn)題,因?yàn)樗麄冊(cè)L問(wèn)的是不同的對(duì)象,所以不存在互斥問(wèn)題。
      2.synchronized代碼塊
      synchronized代碼塊類(lèi)似于以下這種形式:
      synchronized(synObject) {
      }
      當(dāng)在某個(gè)線(xiàn)程中執(zhí)行這段代碼塊,該線(xiàn)程會(huì)獲取對(duì)象synObject的鎖,從而使得其他線(xiàn)程無(wú)法同時(shí)訪問(wèn)該代碼塊。
      synObject可以是this,代表獲取當(dāng)前對(duì)象的鎖,也可以是類(lèi)中的一個(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è)類(lèi)也會(huì)有一個(gè)鎖,它可以用來(lái)控制對(duì)static數(shù)據(jù)成員的并發(fā)訪問(wèn)。
      并且如果一個(gè)線(xiàn)程執(zhí)行一個(gè)對(duì)象的非static synchronized方法,另外一個(gè)線(xiàn)程需要執(zhí)行這個(gè)對(duì)象所屬類(lèi)的static synchronized方法,此時(shí)不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問(wèn)static synchronized方法占用的是類(lèi)鎖,而訪問(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é)果;
      第一個(gè)線(xiàn)程里面執(zhí)行的是insert方法,不會(huì)導(dǎo)致第二個(gè)線(xiàn)程執(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){
    }
    }
      從反編譯獲得的字節(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è)線(xiàn)程對(duì)臨界資源的訪問(wèn)。對(duì)于synchronized方法,執(zhí)行中的線(xiàn)程識(shí)別該方法的 method_info 結(jié)構(gòu)是否有 ACC_SYNCHRONIZED 標(biāo)記設(shè)置,然后它自動(dòng)獲取對(duì)象的鎖,調(diào)用方法,最后釋放鎖。如果有異常發(fā)生,線(xiàn)程自動(dòng)釋放鎖。
      有一點(diǎn)要注意:對(duì)于synchronized方法或者synchronized代碼塊,當(dāng)出現(xiàn)異常時(shí),JVM會(huì)自動(dòng)釋放當(dāng)前線(xiàn)程占用的鎖,因此不會(huì)由于異常導(dǎo)致出現(xiàn)死鎖現(xiàn)象。

    posted on 2014-12-08 21:52 順其自然EVO 閱讀(215) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 測(cè)試學(xué)習(xí)專(zhuān)欄

    <2014年12月>
    30123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    導(dǎo)航

    統(tǒng)計(jì)

    常用鏈接

    留言簿(55)

    隨筆分類(lèi)

    隨筆檔案

    文章分類(lèi)

    文章檔案

    搜索

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 亚洲处破女AV日韩精品| 亚洲?v无码国产在丝袜线观看| 国产亚洲老熟女视频| 成人免费网站视频www| 亚洲伦理一区二区| 你懂的网址免费国产| 黑人大战亚洲人精品一区| 国产精品永久免费视频| 亚洲日韩在线观看| 国产特黄一级一片免费 | 免费高清小黄站在线观看| 中文字幕亚洲日本岛国片| 无套内射无矿码免费看黄| 亚洲av无码乱码在线观看野外 | 久久精品免费大片国产大片| 自拍偷自拍亚洲精品第1页| a在线观看免费网址大全| 亚洲狠狠久久综合一区77777| 亚洲电影在线免费观看| 亚洲中文字幕无码久久2020| 妞干网免费观看视频| 免费一级毛片在线播放视频免费观看永久 | 在线播放免费人成视频在线观看| 亚洲人成电影网站免费| 免费A级毛片无码A∨男男| a级毛片免费全部播放| 亚洲春色在线观看| 国产免费131美女视频| 中文字幕手机在线免费看电影| 亚洲a在线视频视频| 久久精品免费一区二区喷潮| 香蕉视频免费在线播放| 成人人免费夜夜视频观看| 日产久久强奸免费的看| 亚洲国产精品久久久久婷婷软件| 免费毛片a在线观看67194| 黄页网站在线免费观看| 亚洲第一福利视频| 免费永久在线观看黄网站| 少妇无码一区二区三区免费| 亚洲国产精品精华液|