一、ThreadLocal的概念
ThreadLocal并不是线程,而是Thread的一个局部变量,每个线程都有自己的ThreadLocal变量,可以通过get()和set()方法来获取相应值。ThreadLocal设计的初衷是为了解决多线程编程中的资源共享问题,不同于synchronized,ThreadLocal是为每个使用该变量的线程提供独立的变量副本,在各自线程内部,相当于“全局变量”,保证各自线程内操作的是同一个对象,不同于Hashmap数组+链表/红黑树的结构,ThreadLocalmap通过Entry数组保存局部变量,相对于synchronized的“以时间换空间”,ThreadLocal是“以空间换时间”,实现了线程的数据隔离。
二、ThreadLocal原理
源码存在于jdk上,这里不再具体分析,感兴趣可自行探究
- 每个Thread具有ThreadLocal这各局部变量
- ThreadLocalMap是ThreadLocal的内部类,通过Entry存储
- key-value,key是ThreadLocal对象,值是被包装的对象
- set()方法,往ThreadLocalMap设置值,get()方法获取被包装的对象值
这里需要注意的一点是,ThreadLocalMap的生命周期跟Thread一样,ThreadLocal中使用的 key 为 ThreadLocal的弱引用,而 value 是强引用。所以,如果 ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocal中就会出现key为null的Entry。导致value不会被GC,造成内存泄露,所以ThreadLocal的remove()方法需要我们手动调用。
三、代码实例
1、创建Person对象,包含age属性,建立ThreadLocal容器对对象Person进行封装,并提供get/set方法。
package ThreadLocalDemo;
import java.util.Random;
//创建类,年龄属性
class Person {
private int age = 0;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
class MyThreadLocal {
//创建MyThreadLocal作为容器,将Person对象保存于ThreadLocal中
public static final ThreadLocal<Person> threadLocal = new ThreadLocal<>();
public static void set(Person person) {
threadLocal.set(person);
}
//调用remove()方法,防止内存泄漏
public static void remove() {
threadLocal.remove();
}
public static Person get() {
return threadLocal.get();
}
}
public class ThreadLocalDemo1 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Person p = new Person();
int age = (new Random()).nextInt(100);
p.setAge(age);
System.out.println("Person in " + Thread.currentThread().getName() + " set age to " + age);
MyThreadLocal.set(p);
System.out.println("Person in " + Thread.currentThread().getName() + "'s age is " + MyThreadLocal.get().getAge());
}
}).start();
}
}
}
2、SimpleDateFormat是线程不安全的
package ThreadLocalDemo;
import java.text.SimpleDateFormat;
public class ThreadLocalDemo2 {
//SimpleDateFormat非线程安全
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>() {
//重写initialValue方法,赋初始值
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("YYYYMMDDHHMM");
}
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before set formatter in " + Thread.currentThread().getName() + " pattern is " + formatter.get().toPattern());
formatter.set(new SimpleDateFormat());
System.out.println("after set formatter in " + Thread.currentThread().getName() + " pattern is " + formatter.get().toPattern());
}
}).start();
}
}
}
ThreadLocal应用场景:数据库连接、Session管理等。
未完待续......