1. ThreadLocal简介

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

2. ThreadLocal与Synchronized的区别

ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。

但是ThreadLocal与synchronized有本质的区别:

  1. Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

  2. Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本

,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

一句话理解ThreadLocal,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。

3. 兩大使用场景

3.1 典型场景1:

每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有 SimpleDate Format和 Random)

public class ThreadLocalNormalUsage00 {

    private static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 1000; j++) {
            int i = j;
            threadPool.execute(() -> System.out.println(date(i)));
            Thread.sleep(100L);
        }
        threadPool.shutdown();
    }

    public static String date(int seconds) {
        return ThreadSafeFormatter
                .dateFormatThreadLocal
                .get()
                .format(new Date(seconds * 1000L));
    }
}
class ThreadSafeFormatter {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal
            = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}

ThreadLocal-LMLPHP

3.2 典型场景2:

每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦。例如用 Threadlocal保存一些业务内容(用户权限信息、从用户系统取到的用户名、 user id等),这些信息在同一个线程內相同,但是不同的线程使用的业务内容是不相同的。

package com.zeny.threadstudy.threadpool;

public class ThreadLocalNormalUsage06 {
    public static void main(String[] args) {
        new Service1().process();
    }
}
class Service1 {
    public void process() {
        User user = new User("超哥");
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}
class Service2 {
    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service2 get user message: " + user);
        new Service3().process();
    }
}
class Service3 {
    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3 get user message: " + user);
        UserContextHolder.holder.remove();
    }
}
class UserContextHolder {
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
    String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

ThreadLocal-LMLPHP

4.Threadlocal的两个作用

1.让某个需要用到的对象在线程间隔离(每个线程都有自己的独立的对象)

2.在任何方法中都可以轻松获取到该对象

5.ThreadLocal原理

5.1 ThreadLocal的set()方法

 public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
    }

从上面的代码可以看出,ThreadLocal 在set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。那么ThreadLocalMap又是什么呢,还有createMap又是怎么做的,我们继续往下看。大家最后自己再idea上跟下源码,会有更深的认识。
static class ThreadLocalMap {

  static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

ThreadLocal-LMLPHP

可看出ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

//这个是threadlocal 的内部方法
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 
 
    //ThreadLocalMap 构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

5.2 ThreadLocal的get方法

    public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
    }
 
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;
    }

ThreadLocal-LMLPHP

5.3 ThreadLocal的remove()方法

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

remove方法,直接将ThrealLocal 对应的值从当前相差Thread中的ThreadLocalMap中删除。为什么要删除,这涉及到内存泄露的问题。实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例字,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀。

5.4 ThreadLocal与Thread,ThreadLocalMap之间的关系

ThreadLocal-LMLPHP

ThreadLocal-LMLPHP

Thread、THreadLocal、ThreadLocalMap之间啊的数据关系图

从这个图中我们可以非常直观的看出,ThreadLocalMap其实是Thread线程的一个属性值,而ThreadLocal是维护ThreadLocalMap

这个属性指的一个工具类。Thread线程可以拥有多个ThreadLocal维护的自己线程独享的共享变量(这个共享变量只是针对自己线程里面共享)

6.ThreadLocal出现空指针问题

如下代码,当执行get方法的时候,会获取null,然后进行拆箱操作,因为对象为空,所以报空指针异常。

package com.zeny.threadstudy.threadpool;

public class Main {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public int getValue() {
        return threadLocal.get();
    }
    public void setValue(int value) {
        threadLocal.set(value);
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.getValue(); //会出现空指针异常
        main.setValue(10);
        main.getValue();
    }
}

ThreadLocal-LMLPHP

原文链接:史上最全ThreadLocal 详解(一)_倔强的不服的博客-CSDN博客

07-03 00:09