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

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

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

    posts - 1,  comments - 25,  trackbacks - 0

    Preface


    最近看了一下<Java Concurrency In Practice> 這本書, 總體來說還是一本不錯(cuò)的書, 不過粒度不夠細(xì), 是從大的角度, 例如: 設(shè)計(jì)整體項(xiàng)目上如何考慮并發(fā)的多方面因素,不過總體上來說還是一本不錯(cuò)的書,結(jié)合部分網(wǎng)絡(luò)上的資料,總結(jié)一下自己的知識(shí),免的忘了。

    下面是一些最基本的知識(shí),不想再寫了,反正網(wǎng)上多的是,挑了一篇還不錯(cuò)的轉(zhuǎn)過來,大家要支持別人的成果O:
    http://yanxuxin.iteye.com/blog/547261 

    對(duì)于進(jìn)程的概念我們都很熟悉,它是應(yīng)用程序級(jí)的隔離,不同的應(yīng)用程序之間的進(jìn)程幾乎不共享任何資源。而線程則可以說是應(yīng)用程序內(nèi)的隔離,一種相對(duì)低級(jí)別的隔離。一個(gè)進(jìn)程可以有多個(gè)線程,它們之間隔離的內(nèi)容大致包括:a.自身的堆棧,b.程序計(jì)數(shù)器,c.局部變量;共享應(yīng)用的內(nèi)容大致包括:a.內(nèi)存,b.文件句柄,c.進(jìn)程狀態(tài)等。線程不是Java自身的概念,它是操作系統(tǒng)底層的概念。Java作為一種應(yīng)用語言把線程的操作通過API提升到應(yīng)用開發(fā)的支持,但是在并發(fā)性的支持上并不是那么美好。 
       Java在設(shè)計(jì)時(shí),每個(gè)對(duì)象都有一個(gè)隱式的鎖,這個(gè)鎖的使用則是通過synchronized關(guān)鍵字來顯式的使用。在JDK5.0以后引用了java.util.concurrent.ReentrantLock作為synchronized之外的選擇,配和Condition可以以一種條件鎖的機(jī)制來管理并發(fā)的線程,之后的總結(jié)再介紹。提到synchronized,多數(shù)的初學(xué)者都知道Object的wait(),notify(),notifyAll()是配和其使用的,但是為什么要在同步內(nèi)才能用對(duì)象的這些方法呢(不然拋IllegalMonitorStateException)? 
       我想因?yàn)闆]有synchronized讓對(duì)象的隱式鎖發(fā)揮作用,那么方法或者方法塊內(nèi)的線程在同一時(shí)間可能存在多個(gè),假設(shè)wait()可用,它會(huì)把這些線程統(tǒng)統(tǒng)的加到wait set中等待被喚醒,這樣永遠(yuǎn)沒有多余的線程去喚醒它們。每個(gè)對(duì)象管理調(diào)用其wait(),notify()的線程,使得別的對(duì)象即使想幫忙也幫不上忙。這樣的結(jié)果就是多線程永遠(yuǎn)完成不了多任務(wù),基于此Java在設(shè)計(jì)時(shí)使其必須與synchronized一起使用,這樣獲得隱式鎖的線程同一時(shí)間只有一個(gè),當(dāng)此線程被對(duì)象的wait()扔到wait set中時(shí),線程會(huì)釋放這個(gè)對(duì)象的隱式鎖等待被喚醒的機(jī)會(huì),這樣的設(shè)計(jì)會(huì)大大降低死鎖。另外同一個(gè)對(duì)象隱式鎖作用下的多個(gè)方法或者方法塊在沒有鎖的限制下可以同時(shí)允許多個(gè)線程在不同的方法內(nèi)wait和notify,嚴(yán)重的競(jìng)爭(zhēng)條件使得死鎖輕而易舉。所以Java設(shè)計(jì)者試圖通過Monitor Object模式解決這些問題,每個(gè)對(duì)象都是Monitor用于監(jiān)視擁有其使用權(quán)的線程。 
       但是synchronized這種獲得隱式鎖的方式本身也是有隱患問題的:
        a.不能中斷正在試圖獲得鎖的線程,
    b.試圖獲得鎖時(shí)不能設(shè)定超時(shí),
    c.每個(gè)鎖只有一個(gè)條件太少。

    對(duì)于最后一項(xiàng)的設(shè)計(jì)前面提到的JDK5的方案是可以彌補(bǔ)的,一個(gè)ReentrantLock可以有多個(gè)Condition,每個(gè)條件管理獲得對(duì)象鎖滿足條件的線程,通過await(),signalAll()使只關(guān)于Condition自己放倒的線程繼續(xù)運(yùn)行,或者放倒一些線程,而不是全部喚醒等等。但對(duì)于前兩者的極端情況會(huì)出現(xiàn)死鎖。下面的這個(gè)例子: 

    Java代碼  收藏代碼
    1. class DeadLockSample{  
    2.     public final Object lock1 = new Object();  
    3.     public final Object lock2 = new Object();  
    4.   
    5.     public void methodOne(){  
    6.        synchronized(lock1){  
    7.           ...  
    8.           synchronized(lock2){...}  
    9.        }  
    10.     }  
    11.   
    12.     public void methodTwo(){  
    13.        synchronized(lock2){  
    14.       ...  
    15.           synchronized(lock1){...}  
    16.        }  
    17.     }  
    18. }  

    假設(shè)場(chǎng)景:線程A調(diào)用methodOne(),獲得lock1的隱式鎖后,在獲得lock2的隱式鎖之前線程B進(jìn)入運(yùn)行,調(diào)用methodTwo(),搶先獲得了lock2的隱式鎖,此時(shí)線程A等著線程B交出lock2,線程B等著lock1進(jìn)入方法塊,死鎖就這樣被創(chuàng)造出來了。 
       以上的例子不直觀的話,再看一個(gè)實(shí)例順便看看wait()的缺陷: 
    Java代碼  收藏代碼
    1. import java.util.LinkedList;  
    2. import java.util.List;  
    3.   
    4. /** 
    5.  * User: yanxuxin 
    6.  * Date: Dec 9, 2009 
    7.  * Time: 5:58:39 PM 
    8.  */  
    9. public class DeadLockSample {  
    10.     public static void main(String[] args) {  
    11.         final WaitAndNotify wan = new WaitAndNotify();  
    12.   
    13.         Thread t1 = new Thread(new Runnable(){  
    14.             public void run() {  
    15.               wan.pop();  
    16.             }  
    17.         });  
    18.   
    19.         Thread t2 = new Thread(new Runnable(){  
    20.             public void run() {  
    21.               wan.push("a");  
    22.             }  
    23.         });  
    24.   
    25.         t1.start();  
    26.         t2.start();  
    27.    }  
    28. }  
    29.   
    30. class WaitAndNotify {  
    31.   
    32.     final List<String> list = new LinkedList<String>();  
    33.   
    34.     public synchronized void push(String x) {  
    35.         synchronized(list) {  
    36.             list.add(x);  
    37.             notify();  
    38.         }  
    39.     }  
    40.   
    41.     public synchronized Object pop() {  
    42.         synchronized(list) {  
    43.             if(list.size() <= 0) {  
    44.                 try {  
    45.                     wait();  
    46.                 } catch (InterruptedException e) {  
    47.                     e.printStackTrace();  
    48.                 }  
    49.             }  
    50.             return list.size();  
    51.         }  
    52.     }  
    53.   
    54. }  

    上面的這個(gè)例子也會(huì)出現(xiàn)死鎖,為什么呢?首先看WaitAndNotify這個(gè)類,在push和pop方法上有synchronized關(guān)鍵字,方法內(nèi)部也有synchronized,那么當(dāng)WaitAndNotify實(shí)例化時(shí)會(huì)有兩個(gè)對(duì)象的隱式鎖,一個(gè)是WaitAndNotify對(duì)象自身的,作用在方法上;另一個(gè)就是方法內(nèi)部同步用到的list的。主線程開啟兩個(gè)線程t1和t2,t1進(jìn)入pop方法此時(shí)list為空,它先后獲得了wan和list的隱式鎖,接著就被wait扔進(jìn)wait set等待去了。注意這個(gè)wait()方法是誰的?答案是wan的,所以它釋放了wan的隱式鎖,但是把list的死死的抓著不放。此時(shí)t2終于得到了wan的隱式鎖進(jìn)入push方法,但是不幸的是list的隱式鎖它這輩子也得不到了。。。 


    此外synchronized的重點(diǎn)說的簡(jiǎn)單:它就是配和對(duì)象的隱式鎖使用的,注意一定是對(duì)象的隱式鎖!那么下面的這個(gè)例子又怎么解釋呢? 
    Java代碼  收藏代碼
    1. /** 
    2.  * User: yanxuxin 
    3.  * Date: Dec 17, 2009 
    4.  * Time: 9:38:27 PM 
    5.  */  
    6. public class ImplicitLockSample {  
    7.   
    8.     public static void main(String[] args) {  
    9.         final ImplicitLock sample = new ImplicitLock();  
    10.           
    11.         new Thread(new Runnable() {  
    12.             public void run() {  
    13. //                ImplicitLock.method1();  
    14.                 sample.method1();  
    15.             }  
    16.         }).start();  
    17.   
    18.         new Thread(new Runnable() {  
    19.             public void run() {  
    20.                 sample.method2();  
    21.             }  
    22.         }).start();  
    23.     }  
    24. }  
    25.   
    26.   
    27. class ImplicitLock {  
    28.   
    29.     public static synchronized void method1() {  
    30.         System.out.println("method1 executing...");  
    31.         try {  
    32.             Thread.sleep(3000);  
    33.         } catch (InterruptedException e) {  
    34.             e.printStackTrace();  
    35.         }  
    36.     }  
    37.   
    38.     public synchronized void method2() {  
    39.         System.out.println("method2 executing...");  
    40.         try {  
    41.             Thread.sleep(3000);  
    42.         } catch (InterruptedException e) {  
    43.             e.printStackTrace();  
    44.         }  
    45.     }  
    46. }  

    這里ImplicitLock有兩個(gè)同步方法,一個(gè)是static的,一個(gè)是普通的。ImplicitLockSample是一個(gè)測(cè)試主程序,實(shí)例化一個(gè)ImplicitLock對(duì)象,并且開啟兩個(gè)線程,每個(gè)線程分別調(diào)用對(duì)象的method1和method2方法。每個(gè)進(jìn)入方法的線程都會(huì)強(qiáng)制休眠3秒。那么執(zhí)行的現(xiàn)象是什么呢? 
       
       要知道答案有以下幾點(diǎn)要清楚:a.Class和Object的關(guān)系,b.static方法的含義,c.synchronized的機(jī)制,d.sleep的作用。清楚的知道這些之后,一眼就能辨別method1和method2方法上的synchronized配和的是兩把不同的對(duì)象隱式鎖。答案也就清晰的知道這兩個(gè)線程執(zhí)行的打印語句根本就不會(huì)相差近3秒的等待,而是幾乎同時(shí)。下面我試著解釋一下。 

       Class是Object的子類,說明了Class是特殊的對(duì)象,它自然也有對(duì)象隱式鎖。static聲明方法意味著這個(gè)方法不依賴于類的實(shí)例,而是可以理解成去掉了隱式參數(shù)this的,類對(duì)象的方法。synchronized是與對(duì)象隱式鎖綁定的,這代表了將其置于方法聲明上它將與方法的持有對(duì)象綁定。所以method1的同步鎖是ImplicitLock類對(duì)象的隱式鎖,而method2的同步鎖是ImplicitLock實(shí)例對(duì)象的隱式鎖。sleep雖然能讓當(dāng)前的線程休眠,但是它不會(huì)釋放持有的隱式鎖。這樣主程序執(zhí)行是雖然用同一個(gè)實(shí)例讓兩個(gè)線程分別去調(diào)用兩個(gè)方法,但是它們之間并沒有任何競(jìng)爭(zhēng)鎖的關(guān)系,所以幾乎同時(shí)打印,不會(huì)有近3秒的間隔。把method1的調(diào)用改成已注釋的代碼將更容易理解。如果method1的synchronized去掉,或者method2加上synchronized的聲明,那么它們將競(jìng)爭(zhēng)同一個(gè)隱式鎖。先獲得鎖的線程將在3秒后交出鎖,后面的線程才能執(zhí)行打印。 

       寫這篇補(bǔ)遺源自于對(duì)懶漢式單例的重新理解,之前對(duì)synchronized的機(jī)制不明了時(shí),只知道使用synchronized關(guān)鍵字在static方法上聲明就能保證單例的線程安全,但是確不知道那算是誤打誤撞的理解。構(gòu)造這個(gè)驗(yàn)證例子之前,static和synchronized的共同使用讓我對(duì)synchronized隱式鎖有了更清晰的認(rèn)識(shí)。所以打算再寫寫來分享這段體會(huì)。 

    volatile關(guān)鍵字


    Volatile是JDK7里的核心,為什么,隨便看一下LinkedBlockQueue, head 等關(guān)鍵就會(huì)發(fā)現(xiàn)不懂volatile那什么都是扯。。

    volatile就是被認(rèn)為“輕量級(jí)的synchronized”,但是使用其雖然可以簡(jiǎn)化同步的編碼,并且運(yùn)行開銷相對(duì)于JVM沒有優(yōu)化的競(jìng)爭(zhēng)線程同步低,但是濫用將不能保證程序的正確性。鎖的兩個(gè)特性是:互斥和可見。互斥保證了同時(shí)只有一個(gè)線程持有對(duì)象鎖進(jìn)行共享數(shù)據(jù)的操作,從而保證了數(shù)據(jù)操作的原子性,而可見則保證共享數(shù)據(jù)的修改在下一個(gè)線程獲得鎖后看到更新后的數(shù)據(jù)。volatile僅僅保證了無鎖的可見性,但是不提供原子性操作的保證!這是因?yàn)関olatile關(guān)鍵字作用的設(shè)計(jì)是JVM阻止volatile變量的值放入處理器的寄存器,在寫入值以后會(huì)被從處理器的cache中flush掉,寫到內(nèi)存中去。這樣讀的時(shí)候限制處理器的cache是無效的,只能從內(nèi)存讀取值,保證了可見性。從這個(gè)實(shí)現(xiàn)可以看出volatile的使用場(chǎng)景:多線程大量的讀取,極少量或者一次性的寫入,并且還有其他限制。 
       由于其無法保證“讀-修改-寫”這樣操作的原子性(當(dāng)然java.util.concurrent.atomic包內(nèi)的實(shí)現(xiàn)滿足這些操作,主要是通過CAS--比較交換的機(jī)制,后續(xù)會(huì)嘗試寫寫。),所以像++,--,+=,-=這樣的變量操作,即使聲明volatile也不會(huì)保證正確性。圍繞這個(gè)原理的主題,我們可以大致的整理一下volatile代替synchronized的條件:對(duì)變量的寫操作不依賴自身的狀態(tài)。所以除了剛剛介紹的操作外,例如: 
    Java代碼  收藏代碼
    1. private volatile boolean flag;  
    2.   if(!flag) {  
    3.     flag == true;  
    4. }  

    類似這樣的操作也是違反volatile使用條件的,很可能造成程序的問題。所以使用volatile的簡(jiǎn)單場(chǎng)景是一次性的寫入之后,大量線程的讀取并且不再改變變量的值(如果這樣的話,都不是并發(fā)了)。這個(gè)關(guān)鍵字的優(yōu)勢(shì)還是在于多線程的讀取,既保證了讀取的低開銷(與單線程程序變量差不多),又能保證讀到的是最新的值。所以利用這個(gè)優(yōu)勢(shì)我們可以結(jié)合synchronized使用實(shí)現(xiàn)低開銷讀寫鎖: 
    Java代碼  收藏代碼
    1. /** 
    2.  * User: yanxuxin 
    3.  * Date: Dec 12, 2009 
    4.  * Time: 8:28:29 PM 
    5.  */  
    6. public class AnotherSyncSample {  
    7.     private volatile int counter;  
    8.   
    9.     public int getCounter() {   
    10.     return counter;   
    11.     }  
    12.   
    13.     public synchronized void add() {  
    14.         counter++;  
    15.     }  
    16. }  

    這個(gè)簡(jiǎn)單的例子在讀的方法上沒有使用synchronized關(guān)鍵字,所以讀的操作幾乎沒有等待;而由于寫的操作是原子性的違反了使用條件,不能得到保證,所以使用synchronized同步得到寫的正確性保證,這個(gè)模型在多讀取少寫入的實(shí)際場(chǎng)景中應(yīng)該要比都用synchronized的性能有不小的提升。 
       另外還有一個(gè)使用volatile的好處,得自于其原理:內(nèi)部禁止改變兩個(gè)volatile變量的賦值或者初始化順序,并且嚴(yán)格限制volatile變量和其周圍非volatile變量的賦值或者初始化順序。 
    Java代碼  收藏代碼
    1. /** 
    2.  * User: yanxuxin 
    3.  * Date: Dec 12, 2009 
    4.  * Time: 8:34:07 PM 
    5.  */  
    6. public class VolatileTest {  
    7.     public static void main(String[] args) {  
    8.         final VolatileSample sample = new VolatileSample();  
    9.   
    10.         new Thread(new Runnable(){  
    11.             public void run() {  
    12.                 sample.finish();  
    13.             }  
    14.         }).start();  
    15.   
    16.          new Thread(new Runnable(){  
    17.             public void run() {  
    18.                 sample.doSomething();  
    19.             }  
    20.         }).start();  
    21.     }  
    22. }  
    23.   
    24. class VolatileSample {  
    25.     private volatile boolean finished;  
    26.     private int lucky;  
    27.   
    28.     public void doSomething() {  
    29.         if(finished) {  
    30.             System.out.println("lucky: " + lucky);  
    31.         }  
    32.     }  
    33.   
    34.     public void finish() {  
    35.         lucky = 7;  
    36.         finished = true;  
    37.     }  
    38. }  

    這里首先線程A執(zhí)行finish(),完成finished變量的賦值后,線程B進(jìn)入方法doSomething()讀到了finish的值為true,打印lucky的值,預(yù)想狀態(tài)下為7,這樣完美的執(zhí)行結(jié)束了。但是,事實(shí)是如果finished變量不是聲明了volatile的話,過程就有可能是這樣的:線程A執(zhí)行finish()先對(duì)finished賦值,與此同時(shí)線程B進(jìn)入doSomething()得到finished的值為true,打印lucky的值為0,鏡頭切回線程A,接著給lucky賦值為7,可憐的是這個(gè)幸運(yùn)數(shù)字不幸杯具了。因?yàn)檫@里發(fā)生了扯淡的事情:JVM或許為了優(yōu)化執(zhí)行把兩者的賦值順序調(diào)換了。這個(gè)結(jié)果在單線程的程序中簡(jiǎn)直絕對(duì)一定肯定就是不可能,遺憾的是多線程存在這個(gè)隱患。 

    ThreadLocal


    作為一個(gè)JDK5以后支持范型的類,主要是想利用范型把非線程安全的共享變量,封裝成綁定線程的安全不共享變量。這樣的解釋我想我們多半能猜出它的實(shí)現(xiàn)思路:把一個(gè)共享變量在每個(gè)線程使用時(shí),初始化一個(gè)副本,并且和線程綁定。以后所有的線程對(duì)共享變量的操作都是對(duì)線程內(nèi)部那個(gè)副本,完全的線程內(nèi)部變量的操作。 

       要實(shí)現(xiàn)這樣功能類的設(shè)計(jì),主要技術(shù)點(diǎn)是要能把副本和線程綁定映射,程序可以安全查找到當(dāng)前線程的副本,修改后安全的綁定給線程。所以我們想到了Map的存儲(chǔ)結(jié)構(gòu),ThreadLocal內(nèi)部就是使用了線程安全的Map形式的存儲(chǔ)把currentThread和變量副本一一映射。 
    既然要把共享的變成不共享的,那么就要變量滿足一個(gè)場(chǎng)景:變量的狀態(tài)不需要共享。例如無狀態(tài)的bean在多線程之間是安全的,因?yàn)榫€程之間不需要同步bean的狀態(tài),用了就走(很不負(fù)責(zé)啊),想用就用。但是對(duì)于有狀態(tài)的bean在線程之間則必須小心,線程A剛看到狀態(tài)是a,正想利用a做事情,線程B把bean的狀態(tài)改為了b,結(jié)果做了不該做的。但是如果有狀態(tài)的bean不需要共享狀態(tài),每個(gè)線程看到狀態(tài)a或者b都可以做出自己的行為,這種情況下不同步的選擇就是ThreadLocal了。 

       利用ThreadLocal的優(yōu)勢(shì)就在于根本不用擔(dān)心有狀態(tài)的bean為了狀態(tài)的一致而犧牲性能,去使用synchronized限制只有一個(gè)線程在同一時(shí)間做出關(guān)于bean狀態(tài)的行為。而是多個(gè)線程同時(shí)根據(jù)自己持有的bean的副本的狀態(tài)做出行為,這樣的轉(zhuǎn)變對(duì)于并發(fā)的支持是那么的不可思議。例如一個(gè)Dao內(nèi)有個(gè)Connection的屬性,當(dāng)多個(gè)線程使用Dao的同一個(gè)實(shí)例時(shí),問題就來了:多個(gè)線程用一個(gè)Connection,而且它還是有連接,關(guān)閉等等的狀態(tài)轉(zhuǎn)變的,我們很敏感的想到這個(gè)屬性不安全!再看這個(gè)屬性,其實(shí)它是多么的想告訴線程哥哥們:我的這些狀態(tài)根本就不想共享,不要因?yàn)槲业臓顟B(tài)而不敢一起追求。線程哥哥們也郁悶:你要是有多胞胎姐妹該多好啊!這時(shí)候ThreadLocal大哥過來說:小菜,我來搞定!你們這些線程一人一個(gè)Connection,你想關(guān)就關(guān),想連接就連接,再也不用抱怨說它把你的連接關(guān)了。這樣Dao的實(shí)例再也不用因?yàn)樽约河袀€(gè)不安全的屬性而自卑了。當(dāng)然ThreadLocal的思路雖然是很好的,但是官方的說法是最初的實(shí)現(xiàn)性能并不好,隨著Map結(jié)構(gòu)和Thread.currentThread的改進(jìn),性能較之synchronized才有了明顯的優(yōu)勢(shì)。所以要是使用的是JDK1.2,JDK1.3等等,也不要妄想麻雀變鳳凰... 

       再看ThreadLocal和synchronized的本質(zhì)。前者不在乎多占點(diǎn)空間,但是絕對(duì)的忍受不了等待;后者對(duì)等待無所謂,但是就是不喜歡浪費(fèi)空間。這也反映出了算法的一個(gè)規(guī)律:通常是使用場(chǎng)景決定時(shí)間和空間的比例,既省時(shí)又省地的算法多數(shù)情況下只存在于幻想之中。下面寫個(gè)簡(jiǎn)單的例子解釋一下,不過個(gè)人覺得設(shè)計(jì)的例子不太好,以后有實(shí)際的啟發(fā)再替換吧。 
    Java代碼  收藏代碼
    1. import java.util.concurrent.atomic.AtomicInteger;  
    2.   
    3. /** 
    4.  * User: yanxuxin 
    5.  * Date: Dec 14, 2009 
    6.  * Time: 9:26:41 PM 
    7.  */  
    8. public class ThreadLocalSample extends Thread {  
    9.     private OperationSample2 operationSample;  
    10.   
    11.     public ThreadLocalSample(OperationSample2 operationSample) {  
    12.         this.operationSample = operationSample;  
    13.     }  
    14.   
    15.     @Override  
    16.     public void run() {  
    17.         operationSample.printAndIncrementNum();  
    18.     }  
    19.   
    20.     public static void main(String[] args) {  
    21.   
    22.         final OperationSample2 operation = new OperationSample2();//The shared Object for threads.  
    23.   
    24.         for (int i = 0; i < 5; i++) {  
    25.             new ThreadLocalSample(operation).start();  
    26.         }  
    27.     }  
    28. }  
    29.   
    30. class OperationSample {  
    31.     private int num;  
    32.   
    33.     //public synchronized void printAndIncrementNum() {  
    34.     public void printAndIncrementNum() {  
    35.         for (int i = 0; i < 2; i++) {  
    36.             System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");  
    37.             num += 10;  
    38.         }  
    39.     }  
    40. }  
    41.   
    42. class OperationSample2 {  
    43.   
    44.     private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {  
    45.         @Override  
    46.         protected Integer initialValue() {  
    47.             return 0;  
    48.         }  
    49.     };  
    50.   
    51.     public void printAndIncrementNum() {  
    52.         for (int i = 0; i < 2; i++) {  
    53.             int num = threadArg.get();  
    54.             threadArg.set(num + 10);  
    55.             System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");  
    56.         }  
    57.     }  
    58. }  
    59.   
    60. class OperationSample3 {  
    61.   
    62.     private static final AtomicInteger uniqueId = new AtomicInteger(0);  
    63.     private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {  
    64.         @Override  
    65.         protected Integer initialValue() {  
    66.             return uniqueId.getAndIncrement();  
    67.         }  
    68.     };  
    69.   
    70.     public void printAndIncrementNum() {  
    71.         for (int i = 0; i < 2; i++) {  
    72.             int num = threadArg.get();  
    73.             threadArg.set(num + 10);  
    74.             System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");  
    75.         }  
    76.     }  
    77. }  

    這個(gè)例子中ThreadLocalSample繼承自Thread持有OperationSample三個(gè)版本中的一個(gè)引用,并且在線程運(yùn)行時(shí)執(zhí)行printAndIncrementNum()方法。 

       首先看版本1:OperationSample有個(gè)共享變量num,printAndIncrementNum()方法沒有同步保護(hù),方法就是循環(huán)給num賦新值并打印改變值的線程名。因?yàn)闆]有任何的同步保護(hù),所以原本打算每個(gè)線程打印出的值是相鄰遞加10的結(jié)果變成了不確定的遞加。有可能線程1的循環(huán)第一次打印0,第二次就打印50。這時(shí)候我們使用被注釋的方法聲明,結(jié)果就是預(yù)想的同一個(gè)線程的兩次結(jié)果是相鄰的遞加,因?yàn)橥粫r(shí)刻只有一個(gè)線程獲得OperationSample實(shí)例的隱式鎖完成循環(huán)釋放鎖。 

       再看版本2:假設(shè)我們有個(gè)遞增10的簡(jiǎn)單計(jì)數(shù)器,但是是對(duì)每個(gè)線程的計(jì)數(shù)。也就是說我們有一個(gè)Integer計(jì)數(shù)器負(fù)責(zé)每個(gè)線程的計(jì)數(shù)。雖然它是有狀態(tài)的,會(huì)變的,但是因?yàn)槊總€(gè)線程之間不需要共享變化,所以可以用ThreadLocal管理這個(gè)Integer。在這里看到我們的ThreadLocal變量的initialValue()方法被覆寫了,這個(gè)方法的作用就是當(dāng)調(diào)用ThreadLocal的get()獲取線程綁定的副本時(shí)如果還沒綁定則調(diào)用這個(gè)方法在Map中添加當(dāng)前線程的綁定映射。這里我們返回0,表示每個(gè)線程的初始副本在ThreadLocal的Map的紀(jì)錄都是0。再看printAndIncrementNum()方法,沒有任何的同步保護(hù),所以多個(gè)線程可以同時(shí)進(jìn)入。但是,每個(gè)線程通過threadArg.get()拿到的僅僅是自己的Integer副本,threadArg.set(num + 10)的也是自己的副本值。所以結(jié)果就是雖然線程的兩次循環(huán)打印有快有慢,但是每個(gè)線程的兩次結(jié)果都是0和10。 

       最后是版本3:和版本2的不同在于新加了一個(gè)uniqueId的變量。這個(gè)變量是java.util.concurrent.atomic包下的原子變量類。這是基于硬件支持的CAS(比較交換)原語的實(shí)現(xiàn),所以保證了++,--,+=,-=等操作的原子性。所以在ThreadLocal變量的initialValue()方法中使用uniqueId.getAndIncrement()將為每個(gè)線程初始化唯一不會(huì)重復(fù)的遞加1的Integer副本值。而結(jié)果就會(huì)變成5個(gè)線程的首次打印是0~4的5個(gè)數(shù)字,第二次每個(gè)線程的打印是線程對(duì)應(yīng)的首次數(shù)字加10的值。 

       對(duì)于ThreadLocal的使用,Spring的源碼中有大量的應(yīng)用,主要是要支持Singleton的實(shí)例管理,那么自身的一些Singleton的實(shí)現(xiàn)內(nèi)非線程安全的變量,屬性要用ThreadLocal隔離共享。同時(shí)我們?cè)谑褂肧pring的IOC時(shí)也要注意有可能多線程調(diào)用的注冊(cè)到IOC容器的Singleton型實(shí)例是否真的線程安全。另外java.util.concurrent.atomic內(nèi)的原子變量類簡(jiǎn)單的提了一下,再看看怎么能瞎編出東西來吧。
    posted on 2012-06-25 09:37 Daniel 閱讀(1405) 評(píng)論(0)  編輯  收藏 所屬分類: CoreJava
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    常用鏈接

    留言簿(3)

    隨筆檔案

    文章分類

    文章檔案

    相冊(cè)

    搜索

    •  

    最新評(píng)論

    主站蜘蛛池模板: 久久久久久毛片免费播放| 久久久久久久国产免费看| ssswww日本免费网站片| 222www免费视频| 成年女人永久免费观看片| 亚洲中文字幕无码日韩| 亚洲精品456人成在线| 国产精品小视频免费无限app| 国产成人免费高清激情明星| 国产精品久久免费视频| 亚洲无线电影官网| 免费一级特黄特色大片| 99久久99久久精品免费看蜜桃 | 2021在线永久免费视频| 免费吃奶摸下激烈视频| 亚洲国产亚洲综合在线尤物| 91av免费在线视频| 天天天欲色欲色WWW免费| 亚洲国产精品一区| 一级毛片免费一级直接观看| 一个人免费高清在线观看| 亚洲va国产va天堂va久久| 综合一区自拍亚洲综合图区| 91嫩草免费国产永久入口| 亚洲伊人久久精品影院| 爱爱帝国亚洲一区二区三区| 99热在线精品免费全部my| 亚洲va无码va在线va天堂| 日韩在线视频播放免费视频完整版| 114一级毛片免费| 亚洲AV人无码激艳猛片| 一级全免费视频播放| 日韩激情淫片免费看| 亚洲免费黄色网址| 久久久久成人片免费观看蜜芽| 亚洲精品一级无码中文字幕| 亚洲精品美女久久久久久久| 在线免费观看国产| 国产AV无码专区亚洲AV毛网站| 一级毛片在线播放免费| 国产一卡二卡≡卡四卡免费乱码|