目录:
- ThreadLocal介绍
- ThreadLocal使用
- ThreadLocal注意点
ThreadLocal介绍
ThreadLocal是线程的本地变量副本,它是每个线程独立维护的值,不受其它线程的影响。
基本方法:
- public void set(T value):设置当前局部变量的值。
- public T get():获取对应线程局部变量的值。
- public void remove():删除局部变量的值,目的是为了减少内存占用。因线程结束后将会被GC回收,所以此方法并不是必须调用,但它可以加快内存回收的速度。
- public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier):创建一个线程本地变量副本。
- protected T initialValue():为第一次访问变量提供默认值,返回值默认为null,一般会重写此方法。
ThreadLocal使用
public class ThreadLocalDemo { /** * 定义了一个ThreadLocal<Integer>对象, * 并重写它的initialValue方法,初始值是3 * 这个对象会在三个线程间共享 */ private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 3); /** * 设置一个信号量,许可数为1,让三个线程顺序执行 */ private Semaphore semaphore = new Semaphore(1); /** * 一个随机数 */ private Random random = new Random(); /** * 每个线程中调用这个对象的get方法,再调用一个set方法设置一个随机值 */ public class Worker implements Runnable { @Override public void run() { try { // 随机延时1s以内的时间 Thread.sleep(random.nextInt(1000)); // 获取许可 semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } // 从threadLocal中获取值 int value = threadLocal.get(); System.out.println(Thread.currentThread().getName() + " threadLocal old value : " + value); // 修改value值 value = random.nextInt(); // 新的value值放入threadLocal中 threadLocal.set(value); System.out.println(Thread.currentThread().getName() + " threadLocal new value: " + value); System.out.println(Thread.currentThread().getName() + " threadLocal latest value : " + threadLocal.get()); // 释放信号量 semaphore.release(); // 在线程池中,当线程退出之前一定要记得调用remove方法,因为在线程池中的线程对象是循环使用的 threadLocal.remove(); } } /** * 创建三个线程,每个线程都会对ThreadLocal对象进行操作 */ public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(3); ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(); es.execute(threadLocalDemo.new Worker()); es.execute(threadLocalDemo.new Worker()); es.execute(threadLocalDemo.new Worker()); es.shutdown(); } }
你运行之后便可以看出,每个线程第一次调用TheadLocal对象的get方法时都得到初始值3,注意我们上面的代码是让三个线程顺序执行;
显然从运行结果看,第一个线程结束后设置的新值,对第二个线程是没有影响的,第二个线程完成后设置的新值对第三个线程也没有影响。
这就仿佛把ThreadLocal对象当做每个线程内部的对象一样,但实际上threadLocal对象是个外部类对象,内部类Worker访问到的是同一个threadLocal对象,也就是说是被各个线程共享的。
这是如何做到的呢?我们就来看看ThreadLocal对象的内部原理。
ThreadLocal实现原理
1、ThreadLocal中的本地变量都会存储在ThreadLocalMap中,ThreadLocalMap是Thread的一个属性,这也就是它为什么不会受其它线程的影响的原因。
2、ThreadLocalMap是key value结构,内部是Entry。
你可以很容易的通过ThreadLocak的get、set函数证得:
1 public T get() { 2 // 拿到当前线程 3 Thread t = Thread.currentThread(); 4 // 拿到当前线程的threadLocals 5 ThreadLocalMap map = getMap(t); 6 if (map != null) { 7 // 获取ThreadLocals的key map 8 ThreadLocalMap.Entry e = map.getEntry(this); 9 if (e != null) { 10 // 若有这个值,就返出去 11 @SuppressWarnings("unchecked") 12 T result = (T)e.value; 13 return result; 14 } 15 } 16 return setInitialValue(); 17 } 18 19 ThreadLocalMap getMap(Thread t) { 20 return t.threadLocals; 21 } 22 23 public void set(T value) { 24 // 拿到当前线程 25 Thread t = Thread.currentThread(); 26 // 拿到当前线程的threadLocals 27 ThreadLocalMap map = getMap(t); 28 if (map != null) 29 // 若已经初始化了map,则填充值 30 map.set(this, value); 31 else 32 // 若还未初始化map,则创建一个 33 createMap(t, value); 34 }
ThreadLocal注意点
1、内存泄漏:
- 在线程池中,线程执行完后不被回收,而是返回线程池中。
- Thread有个强引用指向ThreadLocalMap,ThreadLocalMap有强引用指向Entry,Entry的key是弱引用的ThreadLocal对象。
- 如果ThreadLocal使用一次后就不在有任何引用指向它,JVM GC会将ThreadLocal对象回收掉,导致Entry变为{null: value}。
- 此时这个Entry已经无效,因为key被回收了,而value无法被回收,一直存在内存中。
解决方案:在执行了ThreadLocal.set()方法之后一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。