Java学习笔记(2)—ThreadLocal

Java学习笔记(2)—ThreadLocal,第1张

Java学习笔记(2)—ThreadLocal Java学习笔记(2)—ThreadLocal
不积小流无以成江海,不积跬步无以至千里
一、概念

ThreadLocal 是一个关于创建线程局部变量的类,主要作用是做数据隔离,保存到 ThreadLocal 中的数据只属于当前线程,该数据对其他线程而言是隔离的。即使用 ThreadLocal 保存的数据只能被当前线程访问,其他线程无法访问和修改。在多线程环境下,防止自己的变量被其他线程篡改。

注意:ThreadLocal 设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题。
1.1 场景
public class Main {

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

    public static void main(String[] args) {
        // 主线程设置值
        threadLocal.set("value1");
        System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
        // 子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程获取的值是:null
                System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
            }
        }).start();

    }
}

场景1:主线程初始化了一个 ThreadLocal 对象 threadLocal,并通过 threadLocal.set() 方法保存了一个值:“value1”,然后使用 threadLocal.get() 拿到设置的值。其中,子线程也使用 threadLocal.get() 去拿值。

问题:取值为null;

public class Main {

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

    public static void main(String[] args) {
        // 主线程设置值
        User user = new User();
        threadLocal.set(user);
        System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
        // 子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(user);
                // 获取到的对象地址是一样的,如果对它的值进行了修改,那么其他线程拿到的值也会改变。
                System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
            }
        }).start();

    }
}

场景2:把变量换成是一个共享的对象保存到 ThreadLocal 中,那么多个线程的 ThreadLocal.get() 取得的还是这个共享对象本身;

问题:还是有并发访问问题。

三、源码分析 3.1 Set方法
public void set(T value) {
  // 获取当前线程
  Thread t = Thread.currentThread();
  // 利用当前线程获取一个 ThreadLocalMap 的对象
  ThreadLocalMap map = getMap(t);
  // 如果上面获取的 ThreadLocalMap 对象不为空,则设置值,否则创建这个 ThreadLocalMap 对象并设置值
  if (map != null)
    map.set(this, value);
  else
    createMap(t, value);
}

  
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

从 set 源码中,我们得知 ThreadLocalMap 是利用当前线程 Thread 作为参数获取的。源码如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

其实,上面的代码获取的是 Thread 对象的 threadLocals 变量。源码如下:

public class Thread implements Runnable {
    省略其他内容...

    
    ThreadLocal.ThreadLocalMap threadLocals = null;

    省略其他内容...
}

结论:可以知道 ThreadLocal 的数据是放入了当前线程的一个ThreadLocalMap 实例中,key 就是 ThreadLocal 对象本身,所以只能在本线程中访问,其他线程无法访问,从而实现了数据隔离。

3.2 get方法
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以 ThreadLocal 对象本身作为 key,获取值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果 ThreadLocalMap 对象不存在,就设置初始值并返回
    // 从下面 setInitialValue() 的源码可知,设置的初始值是一个 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;
}

protected T initialValue() {
    return null;
}

主要工作原理

  • Thread 类中维护着一个 ThreadLocalMap 类型的成员变量。
  • ThreadLocalMap 是一个定义在 ThreadLocal 类中的内部类,是一个 map,用Entry 来进行数据存储。
  • 当调用 ThreadLocal 的 set() 方法时,先获取当前线程的 ThreadLocalMap 对象,然后以 ThreadLocal 对象作为 key 往ThreadLocalMap 中设置值。
  • 当调用 ThreadLocal 的 get() 方法时,也是先获取当前线程的 ThreadLocalMap 对象,以 ThreadLocal 对象作为 key 从 ThreadLocalMap 中获取值。
  • ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程在ThreadLocalMap 中获取或设置值。

参考

Star Zheng 《聊一聊Java中的ThreadLocal》

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5637113.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存