用ThreadLocal为线程生成唯一标识

在多线程编程中,有时候需要自动为每个启动的线程生成一个唯一标识,这个时候,通过一个ThreadLocal变量来保存每个线程的标识是最有效、最方便的方式了。
 
下面是JDK帮助文档的说明:
-------------------------------------------------------------
public class  ThreadLocal<T> extends  Object
 
 
该类提供了线程局部变量。这些变量不同于它们的普通对应物,因为访问一个变量(通过其 get  或 set  方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。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实现只是考虑更周密一些罢了,思想是一致的。
 
 
本文转自 leizhimin 51CTO博客,原文链接:http://blog.51cto.com/lavasoft/94178,如需转载请自行联系原作者
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值