ThreadLocal是lang包下的一个与线程有关的类。该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或者事务ID)相关联。
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且ThreadLocal实例是可访问的。在线程消失后,其线程局部实例的所有副本都会垃圾回收。
ThreadLocal类,可以用来同线程内共享数据而不与其他线程共享数据。有点解释不清楚这个意思。比如,某个任务中,产生一个局部变量,将局部变量传给两个实例对象,实例对象使用这个变量。如果在这个任务中,开启两条或多条线程,如果不使用ThreadLocal或其他方式,那么,每个线程中得到的这个局部变量不是本线程独有的,而是所有线程都共有。
二、示例
1. 不使用ThreadLocal或其他方式,多个线程间数据不能隔离导致的数据污染
/**
* 测试线程间数据污染
*
* @author Administrator
*
*/
public class ThreadSharedDataDirty {
// 线程共享的数据
private static int data = 0;
public static void main(String[] args) {
// 循环开启两条线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
// 业务逻辑,获取随机数,并且调用打印类的打印方法
public void run() {
data = new Random().nextInt();
// 在线程中打印
System.out.println(Thread.currentThread().getName() + ":" + data);
// 分别调用A类和B类的打印业务
new A().get();
new B().get();
}
}).start();
}
}
// 从线程中获取数据,打印的A类
static class A {
public void get() {
System.out.println("A " + Thread.currentThread().getName() + ":" + data);
}
}
// 从线程中获取数据,打印的B类
static class B {
public void get() {
System.out.println("B " + Thread.currentThread().getName() + ":" + data);
}
}
}
2. 使用以线程ID或者线程名字为键以本线程局部变量值为值的map保存线程布局变量列表,避免线程间数据污染
/**
* 使用线程局部变量列表避免线程间共享数据污染
*
* @author Administrator
*
*/
public class ThreadSharedDataIsolate {
// 线程共享的数据
private static int data = 0;
// 线程局部变量与线程的对照存储列表
private static Map<Long, Integer> threadLocalMap = new HashMap<Long, Integer>();
public static void main(String[] args) {
// 循环开启两条线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
// 业务逻辑,获取随机数,并且调用打印类的打印方法
public void run() {
// 为了避免布局变量在存入线程布局变量列表时被污染,将获取随机数据和存放数据的业务同步
// 如果扩大同步的范围,即使不使用线程布局变量列表也可以避免线程污染
// 扩大同步的特点是,1,效率下降;2,这里主要是为展示ThreadLocal类做铺垫,所以,没有使用扩大同步
synchronized (threadLocalMap) {
// 获取随机数
data = new Random().nextInt();
// 将局部变量存放到线程局部变量列表
threadLocalMap.put(Thread.currentThread().getId(), data);
}
System.out.println(Thread.currentThread().getId());
// 在线程中打印
System.out.println(Thread.currentThread().getName() + ":"
+ threadLocalMap.get(Thread.currentThread().getId()));
// 分别调用A类和B类的打印业务
new A().get();
new B().get();
}
}).start();
}
}
// 从线程中获取数据,打印的A类
static class A {
public void get() {
// 从线程布局变量列表中取出本线程的局部变量
int data = threadLocalMap.get(Thread.currentThread().getId());
System.out.println("A " + Thread.currentThread().getName() + ":" + data);
}
}
// 从线程中获取数据,打印的B类
static class B {
public void get() {
// 从线程布局变量列表中取出本线程的局部变量
int data = threadLocalMap.get(Thread.currentThread().getId());
System.out.println("B " + Thread.currentThread().getName() + ":" + data);
}
}
}
3. 使用ThreadLocal类存放简单数据类型时,避免线程共享数据污染问题
/**
* 测试ThreadLocl类存放简单数据类型
*
* @author Administrator
*
*/
public class ThreadLocalSimpleDataTest {
// 创建一个ThreadLocal对象
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 循环开启两条线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
// 业务逻辑,获取随机数,并且调用打印类的打印方法
public void run() {
int random = new Random().nextInt();
// 将随机数放到ThreadLocal中
threadLocal.set(random);
// 在线程中打印
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
// 分别调用A类和B类的打印业务
A.get();
B.get();
}
}).start();
}
}
// 从线程中获取数据,打印的A类
static class A {
public static void get() {
int a = threadLocal.get();
System.out.println("A " + Thread.currentThread().getName() + ":" + a);
}
}
// 从线程中获取数据,打印B类
static class B {
public static void get() {
int a = threadLocal.get();
System.out.println("B " + Thread.currentThread().getName() + ":" + a);
}
}
}
4. 使用ThreadLocal类存放引用数据类型时,避免线程间共享数据污染
使用引用数据类型可以存放多个变量供线程共享。
/**
* 测试ThreadLocl类存放引用数据类型
*
* @author Administrator
*
*/
public class ThreadLocalReferenceDataTest {
// 创建一个ThreadLocal对象
private static ThreadLocal<ThreadData> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 循环开启两条线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
// 业务逻辑,获取随机数,并且调用打印类的打印方法
public void run() {
int random = new Random().nextInt();
// 创建线程数据类
ThreadData data = new ThreadData();
data.setAge(random);
data.setName("name_" + random);
// 将线程数据类放到ThreadLocal中
threadLocal.set(data);
// 在线程中打印
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get().getAge());
// 分别调用A类和B类的打印业务
A.get();
B.get();
}
}).start();
}
}
// 从线程中获取数据,打印的A类
static class A {
public static void get() {
ThreadData data = threadLocal.get();
System.out.println(
"A " + Thread.currentThread().getName() + " name : " + data.getName() + "; age : " + data.getAge());
}
}
// 从线程中获取数据,打印B类
static class B {
public static void get() {
ThreadData data = threadLocal.get();
System.out.println(
"B " + Thread.currentThread().getName() + " name : " + data.getName() + "; age : " + data.getAge());
}
}
}
// 创建一个包含两个属性的类
class ThreadData {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5. 将ThreadLocal封装到引用类中,通过类似饿汉式模式获得引用类的实例,避免线程间共享数据污染
认为是对上面一种方式的升级版,线程内单例,仿照饿汉式单例模式实现。
/**
* 测试ThreadLocl类存放引用数据类型的升级版,使用线程内单例模式
*
* @author Administrator
*
*/
public class ThreadLocalReferenceDataTest2 {
public static void main(String[] args) {
// 循环开启两条线程
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
// 业务逻辑,获取随机数,并且调用打印类的打印方法
public void run() {
int random = new Random().nextInt();
// 获取数据类
ThreadDataHungary data = ThreadDataHungary.getThreadInstance();
data.setAge(random);
data.setName("name_" + random);
// 在线程中打印
System.out.println(
Thread.currentThread().getName() + ":" + ThreadDataHungary.getThreadInstance().getAge());
// 分别调用A类和B类的打印业务
A.get();
B.get();
}
}).start();
}
}
// 从线程中获取数据,打印的A类
static class A {
public static void get() {
ThreadDataHungary data = ThreadDataHungary.getThreadInstance();
System.out.println(
"A " + Thread.currentThread().getName() + " name : " + data.getName() + "; age : " + data.getAge());
}
}
// 从线程中获取数据,打印B类
static class B {
public static void get() {
ThreadDataHungary data = ThreadDataHungary.getThreadInstance();
System.out.println(
"B " + Thread.currentThread().getName() + " name : " + data.getName() + "; age : " + data.getAge());
}
}
}
// 创建一个包含两个属性的类
class ThreadDataHungary {
// 将构造器私有化
private ThreadDataHungary() {
}
// 对外提供静态的方式获取实例
public static ThreadDataHungary getThreadInstance() {
// 从线程局部变量中获取实例
instance = threadLocal.get();
// 如果实例为空,则创建一个实例,并放入线程局部变量
if (null == instance) {
// 创建一个实例
instance = new ThreadDataHungary();
threadLocal.set(instance);
}
// 返回实例
return instance;
}
// 创建一个ThreadLocal类来存放实例
private static ThreadLocal<ThreadDataHungary> threadLocal = new ThreadLocal<ThreadDataHungary>();
// 创建一个null的实例
private static ThreadDataHungary instance = null;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}