线程封闭:
在了解 ThreadLocal 之前,我们先了解下什么是线程封闭.
对象封闭在一个线程里,即使这个对象不是线程安全的,也不会出现并发安全问题。
例如 栈封闭:就是用栈(stack)来保证线程安全.
StringBuilder 是线程不安全的,但是它只是个局部变量,局部变量存储在虚拟 机栈,虚拟机栈是线程隔离的,所以不会有线程安全问题.
ThreadLocal 线程封闭:简单易用 .
使用 ThreadLocal 来实现线程封闭,线程封闭的指导思想是封闭,而不是共享。 所以说 ThreadLocal 是用来解决变量共享的并发安全问题,多少有些不精确。
ThreadLocal 是什么?
ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal 为变量 在每个线程中都创建了一个变量副本,那么每个线程可以访问自己内部的副本变量,使得多个线程间变量不影响.
public class Demo {
static int num = 0; //计数器; 每一个线程都拥有num进行计数
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
num++;
System.out.println(num);
}
}.start();
new Thread(){
@Override
public void run() {
num++;
System.out.println(num);
}
}.start();
}
}
public class ThreadLocalDemo {
//创建一个ThreadLocal对象,用来为每个线程会复制保存一份变量,实现线程封闭
private static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>(){
//初始化 ThreadLocal 中的默认值
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
localNum.set(1); //设置值
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get()+10);
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//11
}
}.start();
new Thread(){
@Override
public void run() {
localNum.set(3);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get()+20);
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//23
}
}.start();
System.out.println(Thread.currentThread().getName()+":"+localNum.get());//0
}
}
ThreadLocal 原理分析 :
首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个 ThreadLocalMap 类对应的 get()、set() 方法.
创建一个ThreadLocal对象;
调用set方法时, 会在底层获取到当前正在执行的线程对象, 为我们的当前线程创建ThreadLocalMap对象;
ThreadLocalMap的键是ThreadLocal对象 , 值就是我们自己set的值;
找的时候,先通过我们自己线程去找对象的ThreadLocalMap.
ThreadLocal 内存泄漏问题:
内存泄漏: 就是有些对象在内存中已经不被使用,但是不能被回收的对象(垃圾对象无法清理).
TreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致 ThreadLocalMap 中 key 为 null, 而 value 还存在着强引用,只有 thead 线程退出以后,value 的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一 直存在一条强引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
解决办法: 在ThreadLoacl中的变量被使用完成后 立即将其删除