1. 为什么要使用线程局部变量你?
如果创建的对象实现了Runnable接口的类的实例,用它作为传入参数,并创建多个线程对象并启动这些线程,那么所有的线程将共享相同的属性。如果在一个线程中改变一个属性,所有线程都会被这个改变影响。这样,不得不处理同步的问题。
要想把线程私有数据(如一个用户ID)和线程关联起来,可以使用线程局部变量 java.lang.ThreadLocal类
2. 什么是线程局部变量?
每个ThreadLocal的实例代表了一个线程局部变量,它为每一条访问线程提供了单独的存储槽(storage slot)。可以把它想象成具有多个槽的变量,然后每条线程可以在同一个变量中存储不同的值。并且,每条线程都只能 看到自己的值,而不会意识到其他线程在这个变量中也有自己的值。
声明方式:ThreadLocal<T>
3.构造函数和方法:
(1)ThreadLocal(): 创建了一个新的线程局部变量。
(2)T get(): 返回调用线程的存储槽中的值。如果当这个线程调用此方法时,值不存在,那么get()会调用 initialValue()方法。
(3)T initialValue(): 创建调用线程的存储槽并存入一个初始(默认)值。默认的初始值是null。
(4)void remove(): 清空调用线程的存储槽。在没有set()方法介入的情况下,如果紧随此方法之后调用get()方法,那么get()方法就会直接调用initialValue()。
(5)void set(T value):设置调用线程的存储槽上的值。
//使用线程局部变量关联不同用户的ID
public class ThreadLocalDemo {
private static volatile ThreadLocal<String> userID = new ThreadLocal<>();
public static void main(String[] args)
{
Runnable r = new Runnable()
{
@Override
public void run()
{
String name = Thread.currentThread().getName();
if(name.equals("A"))
userID.set("foxtrot");
else
userID.set("charlie");
System.out.println(name + " " + userID.get());
}
};
Thread t1 = new Thread(r, "A");
Thread t2 = new Thread(r, "B");
t1.start();
t2.start();
}
}
运行结果:
A foxtrot
B charlie
4. 存储在线程局部变量中的值都是相关的。当一个新的线程被创建出来,它会获得一个新的包含initialValue()值的存储槽。
当想要把值从父类传给子线程时,可以使用InheritableThreadLocal。
InheritableThreadLocal是ThreadLocal的子类,除了InheritableThreadLocal()构造方法,还有:
T childValue(T parentValue):在子线程被创建出来的时候,用父线程的值(即参数)计算出子线程的初始值。
//将一个对象从父线程传到子线程
public class InheritableThreadLocalDemo {
private static volatile InheritableThreadLocal<Integer> intVal = new InheritableThreadLocal<Integer>();
public static void main(String[] args)
{
Runnable rParent = new Runnable()
{
@Override
public void run()
{
intVal.set(10); //父线程在intVal中存储一个值为10的Integer
Runnable rChild = new Runnable()
{
@Override
public void run()
{
String name = Thread.currentThread().getName();
System.out.printf("%s %d%n",name,intVal.get());
}
};
Thread thdChild = new Thread(rChild,"Child");
thdChild.start();
}
};
new Thread(rParent).start();
}
}
主线程创建了一条父线程,这条线程在intVal中存储了一个值为10的java.lang.Integer对象。父线程之后创建了一条子线程,这条线程访问intVal并取得父线程中的Integer对象。