并发编程学习笔记(十、ThreadLocal)

目录:

  • 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有个强引用指向ThreadLocalMapThreadLocalMap有强引用指向EntryEntry的key是弱引用的ThreadLocal对象
  • 如果ThreadLocal使用一次后就不在有任何引用指向它,JVM GC会将ThreadLocal对象回收掉,导致Entry变为{null: value}。
  • 此时这个Entry已经无效,因为key被回收了,而value无法被回收,一直存在内存中。

解决方案:在执行了ThreadLocal.set()方法之后一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。

原文地址:https://www.cnblogs.com/bzfsdr/p/12514073.html