一、什么是 ThreadLocal
1、ThreadLocal 直接翻译为:“线程本地” 或 “本地线程”,如果真的这么认为,那就错了!其实他是一个容器,用于存放线程局部变量,在多线程情况下,确保各个线程变量相互独立,互不干扰。
二、通过代码来理解
1.1、创建一个接口 Sequence
/**
* description: 定义一个 Sequence 接口
* @version v1.0
* @author w
* @date 2018年12月1日下午4:04:53
**/
public interface Sequence {
int getNum();
}
1.2、创建一个ClientThread 类 继承Thread ,输出线程编号和number 结果
/**
* description: ClientThread 输出线程编号和 number 结果
* @version v1.0
* @author w
* @date 2018年12月1日下午4:23:46
**/
public class ClientThread extends Thread {
private Sequence sequence;
public ClientThread(Sequence sequence) {
this.sequence = sequence;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " ---> " +sequence.getNum());
}
}
}
2.1、创建一个 SequenceA 类,实现 Sequence 接口
/**
* description: SequenceA 实现 Sequence 接口
* @version v1.0
* @author w
* @date 2018年12月1日下午4:05:58
**/
public class SequenceA implements Sequence {
// private int num = 0 ; 不添加 static,结果也是一样
private static int num = 0 ;
@Override
public int getNum() {
num = num + 1;
return num;
}
public static void main(String[] args) {
Sequence sequence = new SequenceA();
// 创建3个线程对象
Thread t1 = new ClientThread(sequence);
Thread t2 = new ClientThread(sequence);
Thread t3 = new ClientThread(sequence);
// 启动三个线程
t1.start();
t2.start();
t3.start();
}
}
2.2、输出结果如下 (多线程情况下,每次执行结果,可能都不一样):
Thread-1 ---> 1
Thread-0 ---> 1
Thread-0 ---> 2
Thread-0 ---> 3
Thread-0 ---> 4
Thread-0 ---> 5
Thread-1 ---> 6
Thread-1 ---> 7
Thread-1 ---> 8
Thread-1 ---> 9
Thread-2 ---> 10
Thread-2 ---> 11
Thread-2 ---> 12
Thread-2 ---> 13
Thread-2 ---> 14
2.3、结论: 根据结果可以看到,线程变量被共享了。 要想每个线程独立输出1-5, 就需要用到存放线程变量的容器 ---> ThreadLocal 。
3.1、创建 SequenceB 类实现 Sequence 接口,用ThreadLocal 作为存放线程变量的容器
/**
* description: SequenceB 实现 Sequence 接口,且使用 ThreadLocal 作为存放每个线程变量的容器
* @version v1.0
* @author w
* @date 2018年12月1日 16:14:12
**/
public class SequenceB implements Sequence {
private static final ThreadLocal numContainer = new ThreadLocal(){
@Override
protected Integer initialValue() {
return 0;
};
};
@Override
public int getNum() {
Integer num = numContainer.get();
num = num +1 ;
numContainer.set(num);
return num;
}
public static void main(String[] args) {
// 创建sequence 对象
Sequence sequence = new SequenceB();
// 创建3个线程对象
Thread s1 = new ClientThread(sequence);
Thread s2 = new ClientThread(sequence);
Thread s3 = new ClientThread(sequence);
// 启动3个线程
s1.start();
s2.start();
s3.start();
}
}
3.2、输出结果如下:
Thread-1 ---> 1
Thread-0 ---> 1
Thread-2 ---> 1
Thread-0 ---> 2
Thread-1 ---> 2
Thread-0 ---> 3
Thread-2 ---> 2
Thread-0 ---> 4
Thread-0 ---> 5
Thread-1 ---> 3
Thread-2 ---> 3
Thread-2 ---> 4
Thread-2 ---> 5
Thread-1 ---> 4
Thread-1 ---> 5
3.3、结论:使用 ThreadLocal 作为存放线程变量的容器,实现了每个线程变量独立输出1-5的需求,解决了多线程情况下,变量共享的问题。
三、ThreadLocal 类主要方法
1、protected T initialValue() : 初始化变量值 。(默认:null)
2、public T get() : 从线程局部变量中,获取值。
3、public void set(T value) :将值放入线程局部变量中。
4、public void remove(): 从线程局部变量中移除值。(有助于JVM垃圾回收!)
5、补充: initialValue 方法修饰符是 protected ,提醒大家要给线程局部变量设置一个初始值。
四、仿照 ThreadLocal 写一个功能类似的 MyThreadLocal
1、创建一个 MyThreadLocal
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* description: 自己写一个 java.lang.ThreadLocal ,仿制的山寨版
* @version v1.0
* @author w
* @date 2018年12月1日下午4:37:57
**/
public class MyThreadLocal {
private Map container = Collections.synchronizedMap(new HashMap());
protected T initVariable(){
return null;
}
public T get(){
Thread currentThread = Thread.currentThread();
T value = container.get(currentThread);
if(null == value && !container.containsKey(currentThread)){
value = initVariable();
container.put(currentThread, value);
}
return value ;
}
public void set(T value){
container.put(Thread.currentThread(), value);
}
public void remove(){
container.remove(Thread.currentThread());
}
}
2.1、创建 SequenceC类,使用 MyThreadLocal 作为存放线程变量的容器
/**
* description: 使用 自定义的 MyThreadLocal 实现 ThreadLocal功能,线程变量独立,互不影响。
* @version v1.0
* @author w
* @date 2018年12月1日下午4:48:05
**/
public class SequenceC implements Sequence {
private static MyThreadLocal container = new MyThreadLocal(){
@Override
protected Integer initVariable() {
return 0 ;
};
};
@Override
public int getNum() {
Integer num = container.get();
num = num + 1;
container.set(num);
return num;
}
public static void main(String[] args) {
Sequence sequence = new SequenceC();
Thread t1 = new ClientThread(sequence);
Thread t2 = new ClientThread(sequence);
Thread t3 = new ClientThread(sequence);
t1.start();
t2.start();
t3.start();
}
}
2.2、输出结果如下:
Thread-1 ---> 1
Thread-1 ---> 2
Thread-0 ---> 1
Thread-2 ---> 1
Thread-0 ---> 2
Thread-1 ---> 3
Thread-0 ---> 3
Thread-2 ---> 2
Thread-0 ---> 4
Thread-1 ---> 4
Thread-1 ---> 5
Thread-0 ---> 5
Thread-2 ---> 3
Thread-2 ---> 4
Thread-2 ---> 5
2.3、结论:使用 MyThreadLocal 实现了 ThreadLocal 的功能,保证了线程变量不共享,相互独立。
五、总结
1、ThreadLocal 可以理解为是一个存放局部线程变量的容器。 将当前线程和需要存放的变量,做一个映射,从而保证各个线程互不干扰。
2、作用简单记为: 多线程情况下,保证各个线程变量之间相互独立,互不干扰!
参考资料: 《架构探险:从零开始写Java Web框架》 --- page 169 。
ThreadLocal-面试必问深度解析