用ThreadLocal爲線程生成惟一標識

用ThreadLocal爲線程生成一個標識
 
在多線程編程中,有時候須要自動爲每一個啓動的線程生成一個惟一標識,這個時候,經過一個ThreadLocal變量來保存每一個線程的標識是最有效、最方便的方式了。
 
下面是JDK幫助文檔的說明:
-------------------------------------------------------------
public class ThreadLocal<T> extends Object
 
 
該類提供了線程局部變量。這些變量不一樣於它們的普通對應物,由於訪問一個變量(經過其 getset 方法)的每一個線程都有本身的局部變量,它獨立於變量的初始化副本。 ThreadLocal 實例一般是類中的私有靜態字段,它們但願將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。
 
例如,在下面的類中,私有靜態 ThreadLocal 實例( serialNum)爲調用該類的靜態 SerialNum.get() 方法的每一個線程維護了一個「序列號」,該方法將返回當前線程的序列號。(線程的序列號是在第一次調用 SerialNum.get() 時分配的,並在後續調用中不會更改。)
 
每一個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。
 
ThreadLocal()
          建立一個線程本地變量。
 
方法摘要
 T get()
          返回此線程局部變量的當前線程副本中的值。
protected  T initialValue()
          返回此線程局部變量的當前線程的初始值。
 void remove()
          移除此線程局部變量的值。
 void set(T value)
          將此線程局部變量的當前線程副本中的值設置爲指定值。
 
其中,還給出了一個頗有用的例子片斷,是一個線程序號維護工具,頗有用,我通過完善後以下:
 
/**
* 線程序號標識生成工具
*
* @author leizhimin 2008-8-21 21:28:54
*/

public class SerialNum {
     //類級別的線程編號變量,指向下一個線程的序號
     private static Integer nextNum = 0;
     //定義一個ThreadLocal變量,存放的是Integer類型的線程序號
     private static ThreadLocal<Integer> threadNo = new ThreadLocal<Integer>() {
         //經過匿名內部類的方式定義ThreadLocal的子類,覆蓋initialValue()方法
         public synchronized Integer initialValue() {
             return nextNum++;
        }
    };

     /**
     * 獲取線程序號
     *
     * @return 線程序號
     */

     public int getNextNum() {
         return threadNo.get().intValue();
    }
}
 
/**
* 一個多線程對象,其中有個私有變量SerialNum,用來保存該對象線程的序號
*
* @author leizhimin 2008-8-21 21:52:47
*/

class MultiThreadObject extends Thread {
     //線程序號變量
     private SerialNum serialNum;

     public MultiThreadObject(SerialNum serialNum) {
         //初始化線程序號保存變量
         this.serialNum = serialNum;
    }

     /**
     * 一個示意性的多線程業務方法
     */

     public void run() {
        System.out.println( "線程" + Thread.currentThread().getName() + "的序號爲" + serialNum.getNextNum());
    }
}
 
/**
* 測試線程序號工具
*
* @author leizhimin 2008-8-21 21:50:44
*/

public class TestTreadLocal {
     public static void main(String[] args) {
        SerialNum serialNum = new SerialNum();
        MultiThreadObject m1 = new MultiThreadObject(serialNum);
        MultiThreadObject m2 = new MultiThreadObject(serialNum);
        MultiThreadObject m3 = new MultiThreadObject(serialNum);
        MultiThreadObject m4 = new MultiThreadObject(serialNum);

        m1.start();
        m2.start();
        m3.start();
        m4.start();

         //下面的test方法是在主線程中,當前線程是
         //test();
    }

     public static void test(){
        SerialNum serialNum = new SerialNum();
        System.out.println(serialNum.getNextNum());
        SerialNum serialNum2 = new SerialNum();
        System.out.println(serialNum2.getNextNum());
    }
}
 
運行結果:
線程Thread-0的序號爲0
線程Thread-1的序號爲1
線程Thread-2的序號爲2
線程Thread-3的序號爲3

Process finished with exit code 0
 
JDK中這個例子的高明之處在於巧妙使用靜態變量,結合ThreadLocal的特性,在構件ThreadLocal的時候,經過覆蓋子類的方法來改寫序號。從而達到爲每一個線程生成序號的目的。
 
 
ThreadLocal理解
ThreadLocal是線程的局部變量,經常使用來爲每一個線程提供獨立的變量副本(可理解拷貝),沒個線程能夠隨意改變其副本,而不會影響原版。
ThreadLocal是Java在經過語言的擴展而來的,並不是從語法級(原生)支持。這是致使ThreadLocal概念很差理解的主要緣由。有兩種途徑能夠幫助理解,一是查看ThreadLocal的源代碼(JAVA已經開源了),其次是經過一個簡單的ThreadLocal實現來看其原理。源碼均可以看,但比較複雜。仍是看個簡單ThreadLocal實現吧:
 
import java.util.Map;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

/**
* 一個示意性的ThreadLocal實現,與JDK中ThreadLocal的API對等
*
* @author leizhimin 2008-8-21 22:45:13
*/

public class SimpleThreadLocal {
     //一個線程Map,用來存放線程和其對應的變量副本
     private Map threadMap = Collections.synchronizedMap( new HashMap());

     public void set(Object object) {
        threadMap.put(Thread.currentThread(), object);
    }

     public Object get() {
        Thread currentThread = Thread.currentThread();
        Object obj = threadMap.get(currentThread);
         if (obj == null && !threadMap.containsKey(currentThread)) {
            obj = initialValue();
            threadMap.put(currentThread, obj);
        }
         return obj;
    }

     public void remove() {
        threadMap.remove(Thread.currentThread());
    }

     protected Object initialValue() {
         return null;
    }
}
 
上面這個類能夠替代JDK中ThreadLocal來實現一樣的功能。我已經試過了,只須要修改SerialNum的實現爲:
 
/**
* 線程序號標識生成工具
*
* @author leizhimin 2008-8-21 21:28:54
*/

public class SerialNum {
     //類級別的線程編號變量,指向下一個線程的序號
     private static Integer nextNum = 0;
     //定義一個ThreadLocal變量,存放的是Integer類型的線程序號
//    private static ThreadLocal<Integer> threadNo = new ThreadLocal<Integer>() {
     private static SimpleThreadLocal threadNo = new SimpleThreadLocal() {
         //經過匿名內部類的方式定義ThreadLocal的子類,覆蓋initialValue()方法
         public synchronized Integer initialValue() {
             return nextNum++;
        }
    };

     /**
     * 獲取線程序號
     *
     * @return 線程序號
     */

     public int getNextNum() {
         return (Integer)threadNo.get();
    }
}
 
線程Thread-0的序號爲0
線程Thread-1的序號爲1
線程Thread-2的序號爲2
線程Thread-3的序號爲3

Process finished with exit code 0
 
能夠看出,JDK中TreadLocal實現只是考慮更周密一些罷了,思想是一致的。
相關文章
相關標籤/搜索