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

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

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

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁 ::  :: 聯(lián)系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評(píng)論 :: 0 Trackbacks
    你所不知道的五件事情--多線程編程
    這是IBM developerWorks5 things系列文章中的一篇,講述了關(guān)于多線程的一些應(yīng)用竅門,值得大家學(xué)習(xí)。(2010.11.22最后更新)

    摘要:多線程編程不輕松,但它確實(shí)能幫助理解JVM如何細(xì)微地處理不同代碼結(jié)構(gòu)。Steven Haines將分享的5個(gè)竅門會(huì)幫助你在處理同步方法,volatile變量以及原子類時(shí)做出更為合理的決定。

        盡管很少有Java開發(fā)者能夠忽略多線程編程,且Java平臺(tái)類庫支持它,甚至于更少的開發(fā)者能有時(shí)間去深入學(xué)習(xí)線程。相反,我們只是泛泛地學(xué)習(xí)線程,如果需要的話,會(huì)向我們的工具箱中添加新的技巧和技術(shù)。通過這種方法你可能會(huì)構(gòu)建且運(yùn)行好的應(yīng)用程序,但你還能做得更好。理解Java編譯器和JVM的線程特性,可以幫助你編寫更高效,性能更佳的Java代碼。
        在5 things系列的本期文章中,我會(huì)介紹一些使用同步方法,volatile變量和原子類等多線程編程的細(xì)節(jié)方面。我的討論特別關(guān)注在這些程序結(jié)構(gòu)是如何與JVM和Java編譯器進(jìn)行交互的,以及不同的交互是如何影響Java應(yīng)用程序性能的。

    1. 同步方法與同步塊
        你偶爾會(huì)衡量是否同步整個(gè)方法調(diào)用,或者只是同步方法中線程安全的子塊。在這種情況下,知道Java編譯器在何時(shí)將源代碼轉(zhuǎn)化為字節(jié)碼是有幫助的,它在處理同步方法和同步塊時(shí)是完全不同的。
        當(dāng)JVM在執(zhí)行同步方法時(shí),執(zhí)行線程標(biāo)識(shí)方法的method_info結(jié)構(gòu)設(shè)有ACC_SYNCHRONIZED標(biāo)記,然后它自動(dòng)地獲取對(duì)象的鎖,調(diào)用方法,再釋放鎖。如果發(fā)生了異常,線程會(huì)自動(dòng)釋放鎖。
        另一方面,同步一個(gè)方法塊,繞開JVM內(nèi)建的對(duì)獲取對(duì)象鎖和異常處理的支持,這些功能要顯式的寫在字節(jié)碼中。如果你讀過含有同步塊的方法的字節(jié)碼,你將看到更多的額外操作去管理該功能。清單1展示了生成同步方法與同步塊所產(chǎn)生的調(diào)用:

    清單1. 兩種同步方法
    package com.geekcap;

    public class SynchronizationExample {
        
    private int i;

        
    public synchronized int synchronizedMethodGet() {
            
    return i;
        }

        
    public int synchronizedBlockGet() {
            
    synchronizedthis ) {
                
    return i;
            }
        }
    }

    synchronizedMethodGet()方法生成下列字節(jié)碼:
        0:    aload_0
        
    1:    getfield
        
    2:    nop
        
    3:    iconst_m1
        
    4:    ireturn

    而下面是synchronizedBlockGet()方法的字節(jié)碼:
        0:    aload_0
        
    1:    dup
        
    2:    astore_1
        
    3:    monitorenter
        
    4:    aload_0
        
    5:    getfield
        
    6:    nop
        
    7:    iconst_m1
        
    8:    aload_1
        
    9:    monitorexit
        
    10:    ireturn
        
    11:    astore_2
        
    12:    aload_1
        
    13:    monitorexit
        
    14:    aload_2
        
    15:    athrow

    創(chuàng)建同步塊會(huì)產(chǎn)生16行字節(jié)碼,然而同步方法只返回5行代碼。

    2. ThreadLocal變量
        如果你想為一個(gè)類的所有實(shí)例維護(hù)單個(gè)變量實(shí)例,你將使用靜態(tài)類成員變量來實(shí)現(xiàn)這一點(diǎn)。如果你想在每個(gè)線程中維護(hù)一個(gè)變量的實(shí)例,你將使用thread- local變量。ThreadLocal變量不同于平常的變量,在于每個(gè)線程有它自己的變量初始化實(shí)例,通過get()或set()方法可以訪問這些變量。
        讓我們說,你正在開發(fā)多線程代碼追蹤器的目的是從你的程序去唯一地標(biāo)識(shí)每個(gè)線程的路徑。挑戰(zhàn)在于你需要在跨越多個(gè)線程的多個(gè)類中協(xié)調(diào)多個(gè)方法。沒有 ThreadLocal,這將是一個(gè)很復(fù)雜的問題。當(dāng)一個(gè)線程開始執(zhí)行時(shí),它將生成一個(gè)唯一的標(biāo)記以便于在追蹤器中進(jìn)行標(biāo)識(shí),并在在路徑中將這個(gè)唯一標(biāo)記傳給每個(gè)方法。
        使用ThreadLocal,問題就變得簡(jiǎn)單了。線程在運(yùn)行的開始時(shí)初始化thread-local變量,然后在各個(gè)類的各個(gè)方法中去訪問它,這就能確保該變量只會(huì)在當(dāng)前執(zhí)行線程中維護(hù)路徑信息。當(dāng)線程執(zhí)行完畢時(shí),線程會(huì)將它的特定路徑傳遞給一個(gè)管理對(duì)象,該對(duì)象負(fù)責(zé)維護(hù)所有的路徑。
        當(dāng)你需要基于每個(gè)線程來存儲(chǔ)變量時(shí),使用ThreadLocal就很有意義。

    3. volatile變量
        我估計(jì)一大半Java開發(fā)員知道Java語言含有關(guān)鍵字volatile。其中大約只有10%的人知道它的意義,只有更少的人知道如何高效地使用它。簡(jiǎn)言之,將一個(gè)變量使用volatile關(guān)鍵字進(jìn)行標(biāo)識(shí)就意味著該變量的值將被不同的線程修改。為了充分理解volatile關(guān)鍵字的功用,首先就會(huì)幫助我們理解線程是如何處理非volatile變量的。
        為了改進(jìn)性能,Java語言規(guī)范允許JRE在各個(gè)線程中維護(hù)一份針對(duì)某個(gè)變量的引用的復(fù)本。你能夠認(rèn)為這些變量的"thread-local"復(fù)本類似于緩存,這會(huì)幫助線程避免在每次需要訪問該變量的值時(shí)都去檢查主內(nèi)存。
        但考慮下面場(chǎng)景可能會(huì)發(fā)生的事情:兩個(gè)線程都啟動(dòng)了,第一個(gè)線程讀到變量A的值為5,而第二個(gè)線程讀到變量A的值為10。如果變量A已經(jīng)從5變到10了,然后第一個(gè)線程并不會(huì)意識(shí)到這一變化,所以它會(huì)得到A的錯(cuò)誤值。如果變量A被標(biāo)記為volatile,然后在任何時(shí)候,某個(gè)線程讀取A的值時(shí),它都將查詢 A的主復(fù)本并讀到它的當(dāng)前值。
        如果應(yīng)用中的變量不會(huì)改變,那么使用一個(gè)thread-local緩存將是有意義的。另外,知道volatile關(guān)鍵字能為你做些什么也是很有幫助的。

    4. volatile對(duì)于同步
        如果變量被聲明為volatile,就意味著它會(huì)被多個(gè)線程所修改。很自然地,你會(huì)希望JRE能為volatile變量以某種方式強(qiáng)制執(zhí)行同步。幸運(yùn)地是,當(dāng)訪問volatile變量時(shí),JRE隱式地提供了同步,但會(huì)伴隨一個(gè)很大的代價(jià):讀volatile變量是同步的,寫volatile變量也是同步的,但非原子性操作不能怎么做。
        這就意味著下面的代碼不是線程安全的:
    myVolatileVar++;

    前面的語句可以寫成如下形式:
    int temp = 0;
    synchronize( myVolatileVar ) {
      temp 
    = myVolatileVar;
    }

    temp
    ++;

    synchronize( myVolatileVar ) {
      myVolatileVar 
    = temp;
    }

        換言之,如果一個(gè)volatile變量按上述方法來進(jìn)行更新,即先讀取值,并修改之,然后再賦值,在兩個(gè)同步操作之間,這個(gè)結(jié)果是非線程安全的。你可以考慮是使用同步,還是依賴JRE對(duì)volatile變量的自動(dòng)同步。更好的方法是根據(jù)你的用例:如果賦給volatile變量的值依靠于它的當(dāng)前值(例如加法操作),如果你想操作是線程安全的,那就必須使用同步。

    5. 原子字段更新器
        當(dāng)在多線程環(huán)境中加或減一個(gè)原始數(shù)據(jù)類型時(shí),使用java.util.concurrent包中新添加的原子類會(huì)比編寫你自己的同步代碼塊要好得多。原子類保證能以線程安全的方式來執(zhí)行這些操作,如加減數(shù)值,更新值,以及添加值。原子類包括 AtomicInteger,AtomicBoolean,AtomicLong,AtomicLong等等。
        使用原子類的挑戰(zhàn)在于所有的類方法,包括get,set,以及get-set方法簇都是原子化的。這就意味著read和write操作不會(huì)以同步的方式來修改原子變量的值,也不僅僅重要的讀-更新-寫操作。如果你想對(duì)同步代碼的發(fā)布能有更好的控制,解決方法就是使用原子字段更新器。

    使用原子更新
        原子字段更新器,如AtomicIntegerFieldUpdater,AtomicLongFieldUpdater和 AtomicReferenceFieldUpdater,是用于volatile字段的基本包裝器類。在JDK的內(nèi)部,Java類庫就在使用這些原子類。但在應(yīng)用程序中,它們還未被廣泛使用,你也沒有理由不使用它們。
        清單2展示的示例,是一個(gè)類使用原子更新來改變某人正在閱讀的書:

    清單2. Book類
    package com.geeckap.atomicexample;

    public class Book
    {
        
    private String name;

        
    public Book()
        {
        }

        
    public Book( String name )
        {
            
    this.name = name;
        }

        
    public String getName()
        {
            
    return name;
        }

        
    public void setName( String name )
        {
            
    this.name = name;
        }
    }

    Book類只是一個(gè)POJO(Plain Old Java Object),只有一個(gè)字段:name。

    清單3. MyObject
    package com.geeckap.atomicexample;

    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

    /**
     *
     * 
    @author shaines
     
    */
    public class MyObject
    {
        
    private volatile Book whatImReading;

        
    private static final AtomicReferenceFieldUpdater<MyObject,Book> updater =
                AtomicReferenceFieldUpdater.newUpdater(
                           MyObject.
    class, Book.class"whatImReading" );

        
    public Book getWhatImReading()
        {
            
    return whatImReading;
        }

        
    public void setWhatImReading( Book whatImReading )
        {
            
    //this.whatImReading = whatImReading;
            updater.compareAndSet( thisthis.whatImReading, whatImReading );
        }
    }

        清單3中的MyObject類揭露了whatImReading屬性就是你所期望的,該屬性有g(shù)et和set方法,但set方法做的一些事情不太一樣。不同于簡(jiǎn)單地將內(nèi)部的Book引用賦予一個(gè)特定的Book對(duì)象(使用清單3中被注釋的代碼就可以做到這一點(diǎn)),該示例使用了一個(gè) AtomicReferenceFieldUpdater。

    AtomicReferenceFieldUpdater
    Javadoc對(duì)AtomicReferenceFieldUpdater有如下定義:
        一個(gè)基于反射的工具類,它能對(duì)指定類的指定的volatile引用字段進(jìn)行原子更新。該類被設(shè)計(jì)用于原子數(shù)據(jù)結(jié)構(gòu),在這種結(jié)構(gòu)中,相同節(jié)點(diǎn)的多個(gè)引用字段會(huì)進(jìn)行獨(dú)立地原子更新。
        在清單3中,通過調(diào)用AtomicReferenceFieldUpdater的靜態(tài)方法newUpdater就能創(chuàng)建它的實(shí)例,該方法要接收三個(gè)參數(shù):
        包含該字段的對(duì)象的類(在這個(gè)例子中,就是MyObject)
        將被自動(dòng)更新的對(duì)象的類
        將被自動(dòng)更新的字段的名稱

    在執(zhí)行g(shù)etWhatImReading方法獲取實(shí)際值時(shí)沒有使用任何形式的同步,然而setWhatImReading方法的執(zhí)行則是一個(gè)原子操作。
        清單4證明了如何去使用setWhatImReading()方法,以及如何判斷變量的值進(jìn)行了正確地修改:

    清單4. 練習(xí)原子更新的測(cè)試用例
    package com.geeckap.atomicexample;

    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;

    public class AtomicExampleTest
    {
        
    private MyObject obj;

        @Before
        
    public void setUp()
        {
            obj 
    = new MyObject();
            obj.setWhatImReading( 
    new Book( "Java 2 From Scratch" ) );
        }

        @Test
        
    public void testUpdate()
        {
            obj.setWhatImReading( 
    new Book(
                    
    "Pro Java EE 5 Performance Management and Optimization" ) );
            Assert.assertEquals( 
    "Incorrect book name",
                    
    "Pro Java EE 5 Performance Management and Optimization",
                    obj.getWhatImReading().getName() );
        }

    }

    查看資源以學(xué)習(xí)更多關(guān)于原子類的知識(shí)。

    結(jié)論
        多線程編程總是存在著挑戰(zhàn)性,但涉及到Java平臺(tái),它已經(jīng)獲得了支持去簡(jiǎn)化一些多線程編程任務(wù)。在本文中,我討論了你在基于Java平臺(tái)編寫多線程應(yīng)用時(shí)可能不知道的五件事情,包括同步方法與同步塊的不同之處,使用ThreadLocal變量為每個(gè)線程去存儲(chǔ)值,針對(duì)volatile關(guān)鍵字的廣泛誤解 (包括在需要同步時(shí)依賴volatile所產(chǎn)生的危險(xiǎn)),還簡(jiǎn)要地看了一下原子類的復(fù)雜之處。查看資源以學(xué)習(xí)到更多相關(guān)知識(shí)。

    posted on 2010-11-20 23:49 John Jiang 閱讀(3212) 評(píng)論(5)  編輯  收藏 所屬分類: JavaSE 、Java 、Concurrency 、翻譯

    評(píng)論

    # re: 你所不知道的五件事情--多線程編程(譯) 2010-11-21 00:19 李小武
    mark 明天看吧 睡覺了  回復(fù)  更多評(píng)論
      

    # re: 你所不知道的五件事情--多線程編程(譯) 2010-11-21 13:36 Sha Jiang
    @李小武
    發(fā)布此文后,我也累了,睡覺去了^_^  回復(fù)  更多評(píng)論
      

    # re: 你所不知道的五件事情--多線程編程(譯) 2010-11-28 23:01 豫飄菲
    你在家宅著每天就在研究實(shí)驗(yàn)這些代碼。。。。
    很佩服您老人家堅(jiān)持了這么多年寫這些鬼東東哈:P
    文章還沒來得及細(xì)看哦,下次來踩!
    加油!  回復(fù)  更多評(píng)論
      

    # re: 你所不知道的五件事情--多線程編程(譯) 2010-11-29 09:36 Sha Jiang
    @豫飄菲
    > 你在家宅著每天就在研究實(shí)驗(yàn)這些代碼。。。。
    無聊之人的無聊之舉;-)

    > 很佩服您老人家堅(jiān)持了這么多年寫這些鬼東東哈
    其實(shí)不需要花太多時(shí)間,只是利用一些零散時(shí)間罷了。一般地,一篇文章是分在若干天里完成的,如果周末有時(shí)間的話就會(huì)更快些。平均一個(gè)月也才寫一篇文章,所以不會(huì)占用太多精力。

    > 文章還沒來得及細(xì)看哦,下次來踩!
    歡迎任何意見或建議。

    > 加油!
    A AZ A AZ Fighting!!!  回復(fù)  更多評(píng)論
      

    # re: 你所不知道的五件事情--多線程編程(譯) 2010-11-29 11:55 豫飄菲
    @Sha Jiang
    太啰嗦了  回復(fù)  更多評(píng)論
      

    主站蜘蛛池模板: 在线观看免费黄色网址| 亚洲精品国产成人专区| 100部毛片免费全部播放完整| 亚洲精品无码av片| jizz免费一区二区三区| 特a级免费高清黄色片| 久久久久久久久无码精品亚洲日韩| 亚洲伊人久久大香线蕉在观| 亚洲成A人片在线观看无码不卡| 久久99亚洲综合精品首页| 免费国产美女爽到喷出水来视频| 在线免费观看韩国a视频| 国产免费黄色大片| 久久久久久久久亚洲| 77777_亚洲午夜久久多人| 2022年亚洲午夜一区二区福利| 人人狠狠综合久久亚洲| 老司机精品免费视频| 久久久久久久99精品免费观看| 人妻18毛片a级毛片免费看| 巨波霸乳在线永久免费视频 | 亚洲精品视频在线观看免费| 亚洲视频中文字幕| 亚洲w码欧洲s码免费| 成人午夜性A级毛片免费| 亚洲成年看片在线观看| 亚洲国产婷婷六月丁香| 亚洲国产美女精品久久久久| 亚洲人成色77777在线观看| 免费人人潮人人爽一区二区| 国产免费拔擦拔擦8X高清在线人| 国产美女精品视频免费观看| 日本亚洲视频在线| 永久免费视频网站在线观看| 午夜亚洲AV日韩AV无码大全| 久久国产一片免费观看| 人禽杂交18禁网站免费| 亚洲国产天堂久久久久久| 亚洲av日韩av高潮潮喷无码| 一级大黄美女免费播放| 免费A级毛片av无码|