用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();
}
}
* 线程序号标识生成工具
*
* @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());
}
}
* 一个多线程对象,其中有个私有变量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());
}
}
* 测试线程序号工具
*
* @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
线程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;
}
}
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();
}
}
* 线程序号标识生成工具
*
* @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
线程Thread-1的序号为1
线程Thread-2的序号为2
线程Thread-3的序号为3
Process finished with exit code 0
可以看出,JDK中TreadLocal实现只是考虑更周密一些罢了,思想是一致的。