一、ThreadLocal是什么?
从字面上看“Threadlocal”表示“Thread”的“Local”,其实不然,他并非是线程的本地实现版本,也不是一个Thread,而是Threadlocalvariable(线程局部变量)。线程局部变量其实功能非常简单,就是为每个使用该变量的线程提供一个点亮值的副本,这是java中一种比较特殊的线程绑定机制,每一个线程都可以独立地改变自己的副本,不会和其他线程的副本冲突,不影响其他线程。
二、ThreadLocal API解释(JDK1.7)
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
ThreadLocal提供了一个构造方法和get()、initialValue()、remove()、set(T)四个普通方法。
1、ThreadLocal() 构造方法:创建一个线程本地变量
2、get():返回此线程局部变量的当前线程副本中的值
代码很容易理解,首先我们通过Thread.currentThread得到当前线程,然后获取当前线程的threadLocals变量,这个变量就是ThreadLocalMap类型的。然后根据当前的ThreadLocal实例作为key,获取到Entry对象。
3、initialValue():返回侧线程局部变量的当前线程的“初始值”
4、remove():移除此线程局部变量当前线程的值
5、set(T value):将此线程局部变量的当前线程副本中的值设置为指定值
代码同样很容易理解。同样根据Thread.currentThread得到当前线程,如果当前线程存在threadLocals这个变量不为空,那么根据当前的ThreadLocal实例作为key寻找在map中位置,然后用新的value值来替换旧值。
在ThreadLocal这个类中比较引人注目的应该是ThreadLocal->ThreadLocalMap->Entry这个类。这个类继承自WeakReference。关于弱引用的知识,等有空要好好研究一下。
三、应用示例
我们通过一个具体的实例了解一下ThreadLocal的具体使用方法
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永无BUG 永不修改 //
package com.test.basic;
/**
* @author GX
*
* @date 2017年2月24日 上午10:10:38
*/
public class SequenceNumber {
//A 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
//B 获取下一个序列值
public int getNextNum(Integer num) {
threadLocal.set(threadLocal.get() + num);
return threadLocal.get();
}
public static void main(String[] args) {
SequenceNumber sNumber = new SequenceNumber();
//C 4个线程共享sNumber,各自产生序列号
TestClient t1 = new TestClient(sNumber, 3);
TestClient t2 = new TestClient(sNumber, 5);
TestClient t3 = new TestClient(sNumber, 7);
TestClient t4 = new TestClient(sNumber, 9);
t1.start();
t2.start();
t3.start();
t4.start();
}
private static class TestClient extends Thread {
private SequenceNumber eNumber;
private Integer num;
public TestClient(SequenceNumber sNumber, Integer num) {
this.eNumber = sNumber;
this.num = num;
}
//D 每个线程打出4个序列值
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(
"thread{" + Thread.currentThread().getName() + "} sn{" + eNumber.getNextNum(num) + "}");
}
}
}
}
通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中A处所示。TestClient线程产生一组序列号,在C处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:
考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。