Java進階:Java多線程之ThreadLocal

字號:

ThreadLocal的核心思想很簡單:為每個獨立的線程提供一個變量的副本。
    Java提供的synchronized關鍵字使用了“同步鎖”的機制來阻止線程的競爭訪問,即“以時間換空間”。: " 10pt; FONT-SIZE:>  ThreadLocal則使用了“拷貝副本”的方式,人人有份,你用你的,我用我的,大家互不影響,是“以空間換時間”。每個線程修改變量時,實際上修改的是變量的副本,不怕影響到其它線程。
    為了加深對ThreadLocal的理解,下面我使用一個例子來演示ThreadLocal如何隔離線程間的變量訪問和修改:
    【1】SerialNum類
    packageexample.thread.threadLocal;
    publicclassSerialNum{
    privatestaticintnextSerialNum=1;
    @SuppressWarnings("unchecked")
    privatestaticThreadLocalserialNum=newThreadLocal(){
    protectedsynchronizedObjectinitialValue(){
    returnnewInteger(nextSerialNum++);
    }
    };
    publicstaticintget(){
    return((Integer)(serialNum.get())).intValue();
    }
    @SuppressWarnings("unchecked")
    publicstaticvoidset(IntegernewSerial){
    serialNum.set(newSerial);
    }
    }
    【2】GetSerialNumThread
    packageexample.thread.threadLocal;
    publicclassGetSerialNumThreadimplementsRunnable{
    publicstaticvoidmain(Stringargs[]){
    GetSerialNumThreadserialNumGetter=newGetSerialNumThread();
    Threadt1=newThread(serialNumGetter,"ThreadA");
    Threadt2=newThread(serialNumGetter,"ThreadB");
    t1.start();
    try{
    t1.join();
    }catch(InterruptedExceptione){
    e.printStackTrace();
    }  t2.start();
    }
    publicvoidrun(){
    intmySerialNum=getSerialNum();
    System.out.println("線程"+Thread.currentThread().getName()
    +"獲取到的序列號是"+mySerialNum);
    System.out.println("線程"+Thread.currentThread().getName()
    +"修改了序列號為"+(mySerialNum*3));
    setSerialNum(mySerialNum*3);
    System.out.println("線程"+Thread.currentThread().getName()
    +"再次獲得的序列號是"+getSerialNum());
    }
    privateintgetSerialNum(){
    returnSerialNum.get();
    }
    privatevoidsetSerialNum(intnewSerialNum){
    SerialNum.set(newInteger(newSerialNum));
    }
    }  運行的結果如下:
    線程 Thread A 獲取到的序列號是1
    線程 Thread A 修改了序列號為3
    線程 Thread A 再次獲得的序列號是3
    線程 Thread B 獲取到的序列號是2
    線程 Thread B 修改了序列號為6
    線程 Thread B 再次獲得的序列號是6
    可見第一個線程在調用SerialNum.set(int)方法修改static變量時,其實修改的是它自己的副本,而不是修改本地變量,第二個線程在初始化的時候拿到的序列號是2而不是7。
    為什么會這樣呢?明明serialNum是靜態(tài)變量?。科鋵嵨覀冎恍枰纯碩hreadLocal的內部構造就知道了:
    A. ThreadLocal的get()方法:
    /**
    *Returnsthevalueinthecurrentthread’scopyofthisthread-local
    *variable. Createsandinitializesthecopyifthisisthefirsttime
    *thethreadhascalledthismethod.
    *
    *@returnthecurrentthread’svalueofthisthread-local
    */
    publicTget(){
    Threadt=Thread.currentThread();
    ThreadLocalMapmap=getMap(t);
    if(map!=null)
    return(T)map.get(this);
    //Mapsareconstructedlazily. ifthemapforthisthread
    //doesn’texist,createit,withthisThreadLocalandits
    //initialvalueasitsonlyentry.
    Tvalue=initialValue();
    createMap(t,value);
    returnvalue;
    }
    B. ThreadLocal的set()方法:
    /**
    *Setsthecurrentthread’scopyofthisthread-localvariable
    *tothespecifiedvalue. Manyapplicationswillhavenoneedfor
    *thisfunctionality,relyingsolelyonthe{@link#initialValue}
    *methodtosetthevaluesofthread-locals.
    *
    *@paramvaluethevaluetobestoredinthecurrentthreads’copyof
    *  thisthread-local.
    */
    publicvoidset(Tvalue){
    Threadt=Thread.currentThread();
    ThreadLocalMapmap=getMap(t);
    if(map!=null)
    map.set(this,value);
    else
    createMap(t,value);
    }
    可以看到ThreadLocal在內部維護了一個Map,將變量的值和線程綁定起來,get/set方法都是對該線程對應的value進行操作,所以不會影響到其它線程。