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

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

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

    letter Y A N. G Brass Letter F a n-spo D Pewter Uppercase Letter I N G
    隨筆 - 4, 文章 - 10, 評論 - 2, 引用 - 0
    數(shù)據(jù)加載中……

    Java 理論與實踐: 流行的原子

    十五年前,多處理器系統(tǒng)是高度專用系統(tǒng),要花費數(shù)十萬美元(大多數(shù)具有兩個到四個處理器)。現(xiàn)在,多處理器系統(tǒng)很便宜,而且數(shù)量很多,幾乎每個主要微處理器都內(nèi)置了多處理支持,其中許多系統(tǒng)支持?jǐn)?shù)十個或數(shù)百個處理器。

    要使用多處理器系統(tǒng)的功能,通常需要使用多線程構(gòu)造應(yīng)用程序。但是正如任何編寫并發(fā)應(yīng)用程序的人可以告訴你的那樣,要獲得好的硬件利用率,只是簡單地在多個線程中分割工作是不夠的,還必須確保線程確實大部分時間都在工作,而不是在等待更多的工作,或等待鎖定共享數(shù)據(jù)結(jié)構(gòu)。

    問題:線程之間的協(xié)調(diào)

    如果線程之間 需要協(xié)調(diào),那么幾乎沒有任務(wù)可以真正地并行。以線程池為例,其中執(zhí)行的任務(wù)通常相互獨立。如果線程池利用公共工作隊列,則從工作隊列中刪除元素或向工作隊列添加元素的過程必須是線程安全的,并且這意味著要協(xié)調(diào)對頭、尾或節(jié)點間鏈接指針?biāo)M(jìn)行的訪問。正是這種協(xié)調(diào)導(dǎo)致了所有問題。

    標(biāo)準(zhǔn)方法:鎖定

    在 Java 語言中,協(xié)調(diào)對共享字段的訪問的傳統(tǒng)方法是使用同步,確保完成對共享字段的所有訪問,同時具有適當(dāng)?shù)逆i定。通過同步,可以確定(假設(shè)類編寫正確)具有保護(hù)一組給定變量的鎖定的所有線程都將擁有對這些變量的獨占訪問權(quán),并且以后其他線程獲得該鎖定時,將可以看到對這些變量進(jìn)行的更改。弊端是如果鎖定競爭太厲害(線程常常在其他線程具有鎖定時要求獲得該鎖定),會損害吞吐量,因為競爭的同步非常昂貴。(Public Service Announcement:對于現(xiàn)代 JVM 而言,無競爭的同步現(xiàn)在非常便宜。

    基于鎖定的算法的另一個問題是:如果延遲具有鎖定的線程(因為頁面錯誤、計劃延遲或其他意料之外的延遲),則 沒有要求獲得該鎖定的線程可以繼續(xù)運行。

    還可以使用可變變量來以比同步更低的成本存儲共享變量,但它們有局限性。雖然可以保證其他變量可以立即看到對可變變量的寫入,但無法呈現(xiàn)原子操作的讀-修改-寫順序,這意味著(比如說)可變變量無法用來可靠地實現(xiàn)互斥(互斥鎖定)或計數(shù)器。

    使用鎖定實現(xiàn)計數(shù)器和互斥

    假如開發(fā)線程安全的計數(shù)器類,那么這將暴露 get()increment()decrement() 操作。清單 1 顯示了如何使用鎖定(同步)實現(xiàn)該類的例子。注意所有方法,甚至需要同步 get(),使類成為線程安全的類,從而確保沒有任何更新信息丟失,所有線程都看到計數(shù)器的最新值。


    清單 1. 同步的計數(shù)器類
    public class SynchronizedCounter {
    private int value;
    public synchronized int getValue() { return value; }
    public synchronized int increment() { return ++value; }
    public synchronized int decrement() { return --value; }
    }

    increment()decrement() 操作是原子的讀-修改-寫操作,為了安全實現(xiàn)計數(shù)器,必須使用當(dāng)前值,并為其添加一個值,或?qū)懗鲂轮担羞@些均視為一項操作,其他線程不能打斷它。否則,如果兩個線程試圖同時執(zhí)行增加,操作的不幸交叉將導(dǎo)致計數(shù)器只被實現(xiàn)了一次,而不是被實現(xiàn)兩次。(注意,通過使值實例變量成為可變變量并不能可靠地完成這項操作。)

    許多并發(fā)算法中都顯示了原子的讀-修改-寫組合。清單 2 中的代碼實現(xiàn)了簡單的互斥, acquire() 方法也是原子的讀-修改-寫操作。要獲得互斥,必須確保沒有其他人具有該互斥( curOwner = Thread.currentThread()),然后記錄您擁有該互斥的事實( curOwner = Thread.currentThread()),所有這些使其他線程不可能在中間出現(xiàn)以及修改 curOwner field

    清單 2. 同步的互斥類

    public class SynchronizedMutex {
    private Thread curOwner = null;
    public synchronized void acquire() throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    while (curOwner != null)
    wait();
    curOwner
    = Thread.currentThread();
    }
    public synchronized void release() {
    if (curOwner == Thread.currentThread()) {
    curOwner
    = null;
    notify();
    }
    else
    throw new IllegalStateException("not owner of mutex");
    }
    }

    清單 1 中的計數(shù)器類可以可靠地工作,在競爭很小或沒有競爭時都可以很好地執(zhí)行。然而,在競爭激烈時,這將大大損害性能,因為 JVM 用了更多的時間來調(diào)度線程,管理競爭和等待線程隊列,而實際工作(如增加計數(shù)器)的時間卻很少。您可以回想 上月專欄中的圖,該圖顯示了一旦多個線程使用同步競爭一個內(nèi)置監(jiān)視器,吞吐量將如何大幅度下降。雖然該專欄說明了新的 ReentrantLock 類如何可以更可伸縮地替代同步,但是對于一些問題,還有更好的解決方法。

    鎖定問題

    使用鎖定,如果一個線程試圖獲取其他線程已經(jīng)具有的鎖定,那么該線程將被阻塞,直到該鎖定可用。此方法具有一些明顯的缺點,其中包括當(dāng)線程被阻塞來等待鎖定時,它無法進(jìn)行其他任何操作。如果阻塞的線程是高優(yōu)先級的任務(wù),那么該方案可能造成非常不好的結(jié)果(稱為 優(yōu)先級倒置的危險)。

    使用鎖定還有一些其他危險,如死鎖(當(dāng)以不一致的順序獲得多個鎖定時會發(fā)生死鎖)。甚至沒有這種危險,鎖定也僅是相對的粗粒度協(xié)調(diào)機制,同樣非常適合管理簡單操作,如增加計數(shù)器或更新互斥擁有者。如果有更細(xì)粒度的機制來可靠管理對單獨變量的并發(fā)更新,則會更好一些;在大多數(shù)現(xiàn)代處理器都有這種機制。


     





    回頁首


    硬件同步原語

    如前所述,大多數(shù)現(xiàn)代處理器都包含對多處理的支持。當(dāng)然這種支持包括多處理器可以共享外部設(shè)備和主內(nèi)存,同時它通常還包括對指令系統(tǒng)的增加來支持多處理的特殊要求。特別是,幾乎每個現(xiàn)代處理器都有通過可以檢測或阻止其他處理器的并發(fā)訪問的方式來更新共享變量的指令。

    比較并交換 (CAS)

    支持并發(fā)的第一個處理器提供原子的測試并設(shè)置操作,通常在單位上運行這項操作。現(xiàn)在的處理器(包括 Intel 和 Sparc 處理器)使用的最通用的方法是實現(xiàn)名為 比較并轉(zhuǎn)換或 CAS 的原語。(在 Intel 處理器中,比較并交換通過指令的 cmpxchg 系列實現(xiàn)。PowerPC 處理器有一對名為“加載并保留”和“條件存儲”的指令,它們實現(xiàn)相同的目地;MIPS 與 PowerPC 處理器相似,除了第一個指令稱為“加載鏈接”。)

    CAS 操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當(dāng)前值。)CAS 有效地說明了“我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現(xiàn)在的值即可。”

    通常將 CAS 用于同步的方式是從地址 V 讀取值 A,執(zhí)行多步計算來獲得新值 B,然后使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時更改,則 CAS 操作成功。

    類似于 CAS 的指令允許算法執(zhí)行讀-修改-寫操作,而無需害怕其他線程同時修改變量,因為如果其他線程修改變量,那么 CAS 會檢測它(并失敗),算法可以對該操作重新計算。清單 3 說明了 CAS 操作的行為(而不是性能特征),但是 CAS 的價值是它可以在硬件中實現(xiàn),并且是極輕量級的(在大多數(shù)處理器中):

    清單 3. 說明比較并交換的行為(而不是性能)的代碼

    public class SimulatedCAS {
    private int value;

    public synchronized int getValue() { return value; }

    public synchronized int compareAndSwap(int expectedValue, int newValue) {
    if (value == expectedValue)
    value
    = newValue;
    return value;
    }
    }

    用 CAS 實現(xiàn)計數(shù)器

    基于 CAS 的并發(fā)算法稱為 無鎖定算法,因為線程不必再等待鎖定(有時稱為互斥或關(guān)鍵部分,這取決于線程平臺的術(shù)語)。無論 CAS 操作成功還是失敗,在任何一種情況中,它都在可預(yù)知的時間內(nèi)完成。如果 CAS 失敗,調(diào)用者可以重試 CAS 操作或采取其他適合的操作。清單 4 顯示了重新編寫的計數(shù)器類來使用 CAS 替代鎖定:

    public class CasCounter {
    private SimulatedCAS value;
    public int getValue() {
    return value.getValue();
    }
    public int increment() {
    int oldValue = value.getValue();
    while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)
    oldValue
    = value.getValue();
    return oldValue + 1;
    }
    }

     

    無鎖定且無等待算法

    如果每個線程在其他線程任意延遲(或甚至失敗)時都將持續(xù)進(jìn)行操作,就可以說該算法是 無等待的。與此形成對比的是, 無鎖定算法要求僅 某個線程總是執(zhí)行操作。(無等待的另一種定義是保證每個線程在其有限的步驟中正確計算自己的操作,而不管其他線程的操作、計時、交叉或速度。這一限制可以是系統(tǒng)中線程數(shù)的函數(shù);例如,如果有 10 個線程,每個線程都執(zhí)行一次 CasCounter.increment() 操作,最壞的情況下,每個線程將必須重試最多九次,才能完成增加。)

    再過去的 15 年里,人們已經(jīng)對無等待且無鎖定算法(也稱為 無阻塞算法)進(jìn)行了大量研究,許多人通用數(shù)據(jù)結(jié)構(gòu)已經(jīng)發(fā)現(xiàn)了無阻塞算法。無阻塞算法被廣泛用于操作系統(tǒng)和 JVM 級別,進(jìn)行諸如線程和進(jìn)程調(diào)度等任務(wù)。雖然它們的實現(xiàn)比較復(fù)雜,但相對于基于鎖定的備選算法,它們有許多優(yōu)點:可以避免優(yōu)先級倒置和死鎖等危險,競爭比較便宜,協(xié)調(diào)發(fā)生在更細(xì)的粒度級別,允許更高程度的并行機制等等。

    原子變量類

    在 JDK 5.0 之前,如果不使用本機代碼,就不能用 Java 語言編寫無等待、無鎖定的算法。在 java.util.concurrent.atomic 包中添加原子變量類之后,這種情況才發(fā)生了改變。所有原子變量類都公開比較并設(shè)置原語(與比較并交換類似),這些原語都是使用平臺上可用的最快本機結(jié)構(gòu)(比較并交換、加載鏈接/條件存儲,最壞的情況下是旋轉(zhuǎn)鎖)來實現(xiàn)的。 java.util.concurrent.atomic 包中提供了原子變量的 9 種風(fēng)格( AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean;原子整型;長型;引用;及原子標(biāo)記引用和戳記引用類的數(shù)組形式,其原子地更新一對值)。

    原子變量類可以認(rèn)為是 volatile 變量的泛化,它擴展了可變變量的概念,來支持原子條件的比較并設(shè)置更新。讀取和寫入原子變量與讀取和寫入對可變變量的訪問具有相同的存取語義。

    雖然原子變量類表面看起來與清單 1 中的 SynchronizedCounter 例子一樣,但相似僅是表面的。在表面之下,原子變量的操作會變?yōu)槠脚_提供的用于并發(fā)訪問的硬件原語,比如比較并交換。

    更細(xì)粒度意味著更輕量級

    調(diào)整具有競爭的并發(fā)應(yīng)用程序的可伸縮性的通用技術(shù)是降低使用的鎖定對象的粒度,希望更多的鎖定請求從競爭變?yōu)椴桓偁帯逆i定轉(zhuǎn)換為原子變量可以獲得相同的結(jié)果,通過切換為更細(xì)粒度的協(xié)調(diào)機制,競爭的操作就更少,從而提高了吞吐量。

    ABA 問題
    因為在更改 V 之前,CAS 主要詢問“V 的值是否仍為 A”,所以在第一次讀取 V 以及對 V 執(zhí)行 CAS 操作之前,如果將值從 A 改為 B,然后再改回 A,會使基于 CAS 的算法混亂。在這種情況下,CAS 操作會成功,但是在一些情況下,結(jié)果可能不是您所預(yù)期的。(注意, 清單 1 和 清單 2 中的計數(shù)器和互斥例子不存在這個問題,但不是所有算法都這樣。)這類問題稱為 ABA 問題,通常通過將標(biāo)記或版本編號與要進(jìn)行 CAS 操作的每個值相關(guān)聯(lián),并原子地更新值和標(biāo)記,來處理這類問題。 AtomicStampedReference 類支持這種方法。

    java.util.concurrent 中的原子變量

    無論是直接的還是間接的,幾乎 java.util.concurrent 包中的所有類都使用原子變量,而不使用同步。類似ConcurrentLinkedQueue 的類也使用原子變量直接實現(xiàn)無等待算法,而類似 ConcurrentHashMap 的類使用 ReentrantLock 在需要時進(jìn)行鎖定。然后, ReentrantLock 使用原子變量來維護(hù)等待鎖定的線程隊列。

    如果沒有 JDK 5.0 中的 JVM 改進(jìn),將無法構(gòu)造這些類,這些改進(jìn)暴露了(向類庫,而不是用戶類)接口來訪問硬件級的同步原語。然后,java.util.concurrent 中的原子變量類和其他類向用戶類公開這些功能。





    回頁首


    使用原子變量獲得更高的吞吐量

    上月,我介紹了 ReentrantLock 如何相對于同步提供可伸縮性優(yōu)勢,以及構(gòu)造通過偽隨機數(shù)生成器模擬旋轉(zhuǎn)骰子的簡單、高競爭示例基準(zhǔn)。我向您顯示了通過同步、 ReentrantLock 和公平 ReentrantLock 來進(jìn)行協(xié)調(diào)的實現(xiàn),并顯示了結(jié)果。本月,我將向該基準(zhǔn)添加其他實現(xiàn),使用 AtomicLong 更新 PRNG 狀態(tài)的實現(xiàn)。

    清單 5 顯示了使用同步的 PRNG 實現(xiàn)和使用 CAS 備選實現(xiàn)。注意,要在循環(huán)中執(zhí)行 CAS,因為它可能會失敗一次或多次才能獲得成功,使用 CAS 的代碼總是這樣。


    清單 5. 使用同步和原子變量實現(xiàn)線程安全 PRNG
                public class PseudoRandomUsingSynch implements PseudoRandom {
                private int seed;
                public PseudoRandomUsingSynch(int s) { seed = s; }
                public synchronized int nextInt(int n) {
                int s = seed;
                seed = Util.calculateNext(seed);
                return s % n;
                }
                }
                public class PseudoRandomUsingAtomic implements PseudoRandom {
                private final AtomicInteger seed;
                public PseudoRandomUsingAtomic(int s) {
                seed = new AtomicInteger(s);
                }
                public int nextInt(int n) {
                for (;;) {
                int s = seed.get();
                int nexts = Util.calculateNext(s);
                if (seed.compareAndSet(s, nexts))
                return s % n;
                }
                }
                }
                

    下面圖 1 和圖 2 中的圖與上月那些圖相似,只是為基于原子的方法多添加了一行。這些圖顯示了在 8-way Ultrasparc3 和單處理器 Pentium 4 上使用不同數(shù)量線程的隨機發(fā)生的吞吐量(以每秒轉(zhuǎn)數(shù)為單位)。測試中的線程數(shù)不是真實的;這些線程所表現(xiàn)的競爭比通常多得多,所以它們以比實際程序中低得多的線程數(shù)顯示了 ReentrantLock 與原子變量之間的平衡。您將看到,雖然 ReentrantLock 擁有比同步更多的優(yōu)點,但相對于 ReentrantLock,原子變量提供了其他改進(jìn)。(因為在每個工作單元中完成的工作很少,所以下圖可能無法完全地說明與 ReentrantLock 相比,原子變量具有哪些可伸縮性優(yōu)點。)


    圖 1. 8-way Ultrasparc3 中同步、ReentrantLock、公平 Lock 和 AtomicLong 的基準(zhǔn)吞吐量
    8-way Ultrasparc3 吞吐量 

    圖 2. 單處理器 Pentium 4 中的同步、ReentrantLock、公平 Lock 和 AtomicLong 的基準(zhǔn)吞吐量
    Uniprocessor Pentium4 吞吐量 

    大多數(shù)用戶都不太可能使用原子變量自己開發(fā)無阻塞算法 — 他們更可能使用 java.util.concurrent 中提供的版本,如 ConcurrentLinkedQueue。但是萬一您想知道對比以前 JDK 中的相類似的功能,這些類的性能是如何改進(jìn)的,可以使用通過原子變量類公開的細(xì)粒度、硬件級別的并發(fā)原語。

    開發(fā)人員可以直接將原子變量用作共享計數(shù)器、序號生成器和其他獨立共享變量的高性能替代,否則必須通過同步保護(hù)這些變量。





    回頁首


    結(jié)束語

    JDK 5.0 是開發(fā)高性能并發(fā)類的巨大進(jìn)步。通過內(nèi)部公開新的低級協(xié)調(diào)原語,和提供一組公共原子變量類,現(xiàn)在用 Java 語言開發(fā)無等待、無鎖定算法首次變?yōu)榭尚小H缓螅?#160;java.util.concurrent 中的類基于這些低級原子變量工具構(gòu)建,為它們提供比以前執(zhí)行相似功能的類更顯著的可伸縮性優(yōu)點。雖然您可能永遠(yuǎn)不會直接使用原子變量,還是應(yīng)該為它們的存在而歡呼。

    posted on 2008-10-03 14:35 rainman 閱讀(239) 評論(0)  編輯  收藏 所屬分類: java多線程

    主站蜘蛛池模板: 57pao国产成永久免费视频| 亚洲xxxx18| 美女黄色免费网站| 免费看a级黄色片| 亚洲国产一区二区三区在线观看 | 亚洲av无码片vr一区二区三区 | 亚洲AV无码国产精品麻豆天美| 高清永久免费观看| 亚洲日产韩国一二三四区| 最近免费中文字幕中文高清| 精品久久久久久亚洲| 免费观看又污又黄在线观看| 亚洲国产精品一区二区三区久久| 亚洲天堂免费在线视频| 亚洲第一AAAAA片| 午夜影视在线免费观看| 免费在线观看一级片| 亚洲美女中文字幕| 操美女视频免费网站| 久久亚洲欧美国产精品| 精品亚洲国产成AV人片传媒| 91成人免费观看网站| 亚洲AV无码之国产精品| 亚洲精品美女在线观看| 午夜一级毛片免费视频| 日本免费久久久久久久网站| 亚洲伦理中文字幕| 国产亚洲一区二区三区在线观看| 日韩高清在线免费看| 久久精品免费网站网| 亚洲精品熟女国产| 亚洲精品无码久久久久去q| 国产中文字幕免费观看| 一个人免费播放在线视频看片| 久久亚洲AV无码精品色午夜| 亚洲日韩人妻第一页| 5555在线播放免费播放| 中国videos性高清免费| 日本视频免费观看| 国产亚洲精品精品精品| 亚洲AV日韩AV永久无码久久|