ThreadLocal
1.定义
线程本地的变量副本,属于每个线程独有。每个线程使用ThreadLocal设置自己的值,设置的值之间不受影响,但是使用同一个ThreadLocal对象。所以设置的每个变量,是给每个线程一个独有的变量副本
2.简单使用
public class HelloThreadLocal {
    private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>();
    public static void main(String[] args) {
        //线程1 使用threadLocal设置自己的变量副本
        new Thread(() -> {
            threadLocal.set(new Loan("zhangsan", "1000.00"));
            System.out.println("线程-1loan:" + threadLocal.get());
        }).start();
        //线程2 使用threadLocal设置自己的变量副本
        new Thread(() -> {
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
            }
            HelloThreadLocal.Loan loan = threadLocal.get();
            System.out.println("线程-2loan:" + loan);
            threadLocal.set(new Loan("lisi", "2000.00"));
            loan = threadLocal.get();
            System.out.println("线程-2loan:" + loan);
        }).start();
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
        }
        System.out.println("main-线程loan:" + threadLocal.get());
        threadLocal.set(new Loan("wangwu", "1000.00"));
        System.out.println("main-线程loan:" + threadLocal.get());
    }
    @Data
    @AllArgsConstructor
    public static class Loan {
        private String name;
        private String amount;
    }
}
输出结果:
线程-1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)
线程-2loan:null
线程-2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)
main-线程loan:null
main-线程loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)
可以看出,每个线程无法获取到其它线程设置的loan对象
3.源码分析
1.get方法
首先获取当前线程的ThreadLocalMap变量,如果map为空的话调用setInitialValue方法返回默认值,如果map不为空获取entry中key对应的value值
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
getMap方法:
threadLocals变量用于存储当前线程自身的ThreadLocal,所以虽然使用的是ThreadLocal的get方法,但是操作的实际是当前线程的threadLocals本地遍历副本的Map
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
setInitialValue方法:
首先初始化value为null,然后当前线程获取map,如果为null的话,创建map,否则直接set,这里会返回null,所以get方法也会返回null的值
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
2.set方法
如果是第一次使用set方法,创建一个默认大小为16的ThreadLocalMap,并将key设为ThreadLocalMap对象,value用传入的value,如果不是第一次直接map.set
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.ThreadLocalMap
ThreadLocalMap中的核心结构是一个Entry,用来存储key-value数据:
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
这里继承了弱引用,弱引用是只要gc发现了它,就会回收掉,这里的key使用弱引用是为了防止内存泄漏,因为如果key是强引用的话,当一个threadLocal对象为null后,还是会指向它,导致该对象不能被回收,造成内存泄漏,这里虽然key使用了弱引用,但是还是存在value指向的强引用,所以需要用remove方法来删除之前的key,不然还是会造成内存泄漏
4.应用场景
1.Spring的Transaction机制中,将一个线程中的事务放入ThreadLocal中,可以在整个方法调用栈中随时取出事务的信息进行操作,不会影响其它线程
2.Log4j2等日志框架中的MDC
3.HDFS edits_log的txId自增后放入线程本地副本,HDFS的每条edit_log都有一个txId,会将这个txId记录到当前线程方便在整个线程过程中随时取用
小结:
ThreadLocal最常用的2个场景就是:
1.线程中,在各个方法需要共享变量时使用。除了方法之间传递入参,通过ThreadLocal可以很方便的做到这一点
2.多线程操作时,防止并发冲突,保证线程安全。比如一般会拷贝一份数据到线程本地,自己修改本地变量,是线程安全的
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号