ThreadLocal解析
今天是:
我需要三件东西:爱情友谊和图书。然而这三者之间何其相通!炽热的爱情可以充实图书的内容,图书又是人们最忠实的朋友。
User Image ThreadLocal解析 23/Nov/2021

 1.概述

ThreadLocal将存储的数据和线程关联起来,来解决并发情况下数据一致性问题,  本文将介绍ThreadLocal的使用,结构和原理。

2.ThreadLocal 介绍

该类提供了线程局部变量,访问的是线程都有自己的、独立初始化的变量副本。ThreadLocal通常将一个类的私有静态变量状态和线程关联起来。每个线程都持有对其线程局部变量副本的隐式引用, 只要线程处于活动状态并且可以访问 ThreadLocal 实例; 线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)

public class ThreadLocalDemo {
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<>();

    public static int get() {
        return threadId.get();
    }

    public static void set() {
        threadId.set(nextId.incrementAndGet());
    }


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                ThreadLocalDemo.set();
                System.out.println("Thread name=>"+Thread.currentThread().getName()+":"+ThreadLocalDemo.threadId.get());
            },"Thread"+i);
            thread.start();
        }
        Thread.sleep(2000);
        System.out.println("Thread name"+Thread.currentThread().getName()+":"+ ThreadLocalDemo.nextId);
    }
}
//output
Thread name=>Thread0:1
Thread name=>Thread2:4
Thread name=>Thread3:5
Thread name=>Thread4:3
Thread name=>Thread1:2
Thread namemain:5

如上程序类类的有一个ThreadLocal类型的成员变量threadId,启动线程将值加一,每个线程自己持有的threadId都不一样。而nextId是线程共享的。

3.源码分析

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
//设置值的时候,先判断map有没有创建,如果没有创建则创建,如果已经创建,则将线程和要设置的值关联起来
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;//返回线程关联的ThreadLocal.ThreadLocalMap threadLocals
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap并且设置初始化值
    }

//get方法
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//根据key拿到ThreadLocalMap.Entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
//Entry 结构
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

4.线程池中使用ThreadLocal

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
        for (int i = 0; i <3 ; i++) {
            Runnable runnable = () -> {
                ThreadLocalDemo.set();
                System.out.println("Thread name=>" + Thread.currentThread().getName() + ":" + ThreadLocalDemo.threadId.get());
            };

            threadPoolExecutor.execute(runnable);
        }
        boolean flag=true;
        while (flag) {
            if (threadPoolExecutor.getCompletedTaskCount()==3) {

                Runnable runnable = () -> {
                    //ThreadLocalDemo.set();
                    System.out.println("执行第二次任务 Thread name=>" + Thread.currentThread().getName() + ":" + ThreadLocalDemo.threadId.get());
                };

                threadPoolExecutor.execute(runnable);
                flag=false;
            }
        }
        threadPoolExecutor.shutdown();
    }

//output
Thread name=>pool-1-thread-1:1
Thread name=>pool-1-thread-2:2
Thread name=>pool-1-thread-3:3
执行第二次任务 Thread name=>pool-1-thread-1:1

看上面代码第一批任务执行完成,线程归还到线程池中,且保存了ThreadLocal数据,当其他任务再次进来时从池中获取了原来的线程执行任务,这样的话会有使用旧的线程处理新的请求,如果操作不当,可能会出现请求处理错误问题。

5.1

  • 解决办法 在finally中remove掉
  • 重写ThreadPoolExecutor.afterExecute
分享到:

专栏

类型标签

外部链接

网站访问总量