用ThreadLocal为线程生成唯一标识

用ThreadLocal为线程生成一个标识
 
在多线程编程中,有时候需要自动为每个启动的线程生成一个唯一标识,这个时候,通过一个ThreadLocal变量来保存每个线程的标识是最有效、最方便的方式了。
 
下面是JDK帮助文档的说明:
-------------------------------------------------------------
public class ThreadLocal<T> extends Object
 
 
该类提供了线程局部变量。这些变量不同于它们的普通对应物,因为访问一个变量(通过其 getset 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。 ThreadLocal 实例通常是类中的私有静态字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
 
例如,在下面的类中,私有静态 ThreadLocal 实例( serialNum)为调用该类的静态 SerialNum.get() 方法的每个线程维护了一个“序列号”,该方法将返回当前线程的序列号。(线程的序列号是在第一次调用 SerialNum.get() 时分配的,并在后续调用中不会更改。)
 
每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
 
ThreadLocal()
          创建一个线程本地变量。
 
方法摘要
 Tget()
          返回此线程局部变量的当前线程副本中的值。
protected  TinitialValue()
          返回此线程局部变量的当前线程的初始值。
 voidremove()
          移除此线程局部变量的值。
 voidset(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实现只是考虑更周密一些罢了,思想是一致的。
 
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值