ThreadLocal,顾名思义,这是本地线程的意思,不好意思,这次顾错了。
ThreadLocal类是用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
按照我的理解,ThreadLocal就像是一个存储线程公共值的map,key是当前线程,value是这个公共值,各个线程之间对该公共值的使用互不影响,相互独立。
一、案例引入
举一个例子来说明一下,如每个人刚出生都是0岁,假设每个人能活100年,那么他去世的年龄就是100岁。
1、(不使用ThreadLocal情况下)
先定义一个Person类:
package com.bxw.concurrent.threadLocal;
public class Person {
private static int age = 0;
public void grow(){
age++;
}
public int getAge(){
return age;
}
}
再定义一个PersonThread类:
package com.bxw.concurrent.threadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PersonThread extends Thread{
private Person person;
PersonThread(Person person){
this.person = person;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"的出生年龄是" + person.getAge());
for(int i = 0; i < 100; i++){
person.grow();
}
System.out.println(Thread.currentThread().getName()+"的死亡年龄是" + person.getAge());
}
public static void main(String[] args) {
Person person = new Person();
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int i = 0; i < 4; i++){
executorService.submit(new PersonThread(person));
}
executorService.shutdown();
}
}
运行结果:
可以看到四个线程的死亡年龄分别是100,200,300,400。这就说明了这四个线程共享了年龄这个静态变量,线程安全出了问题。
那么如何让每个线程都独享一个静态变量呢,看看下面对Person的改造
2、(使用ThreadLocal情况下)
package com.bxw.concurrent.threadLocal;
public class Person {
private static int age = 0;
private static MyThreadLocal ageLocal = new MyThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void grow(){
ageLocal.set(ageLocal.get() + 1);
}
public int getAge(){
return ageLocal.get();
}
}
PersonThread不用改变,再次运行,运行结果:
这时,每个线程的出生年龄都是0,死亡年龄都是100,说明每个线程都拥有自己的静态变量,相互独立,不相互影响。
调整下PersonThread中线程池线程个数
ExecutorService executorService = Executors.newFixedThreadPool(4);
再次运行,可以看到线程三,活了两个100年,那么线程三死亡的时候年龄是200,再次说明这个静态变量被单个线程独立使用。
三、ThreadLocal方法介绍
public void set(T value):将值放入线程局部变量中
public T get():从线程局部变量中获取值
public void remove():从线程局部变量中移除值
protected T initialValue():线程局部变量初始值(initialValue时protected的,就是要提醒这个方法要重写)
四、实现穷人版ThreadLocal
了解了ThreadLocal的基本原理,可以仿照ThreadLocal写个简易版的ThreadLocal
package com.bxw.concurrent.threadLocal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal {
private Map container = Collections.synchronizedMap(new HashMap());
public void set(T value) {
container.put(Thread.currentThread(), value);
}
public T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if (value == null && !container.containsKey(thread)) {
value = initialValue();
container.put(thread, value);
}
return value;
}
public void remove() {
container.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
将PersonThread中的ThreadLocal替换成MyThreadLocal,
private static MyThreadLocal ageLocal = new MyThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
运行结果与ThreadLocal一模一样。。。
五、JDK中使用ThreadLocal的类(比如BigDecimal)
BigDecimal中有一个静态内部类StringBuilderHelper,BigDecimal中使用到了ThreadLocal设置了初始值(一个单独的StringBuilderHelper对象),就可以实现让BigDecimal在多线程环境下,每个线程都拥有一个StringBuilderHelper对象。
六、总结
当一个类中的变量(无论是否静态)在并发条件下需要考虑线程安全,就需要使用ThreadLocal,让ThreadLocal保证每个线程都独立拥有自己的变量。