ThreadLocal

Posted marklogzhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。

一、ThreadLocal 介绍

1.1 ThreadLocal 是什么?

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

1.2 ThreadLocal特性

ThreadLocalSynchronized 都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized 通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal 是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于 SynchronizedThreadLocal 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

1.3 使用场景

  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  • 线程间数据隔离
  • 进行事务操作,用于存储线程事务信息
  • 数据库连接,Session会话管理

1.4 ThreadLocal 方法介绍

// 设置值
void set(T value);
// 获取值
T get();
// 删除值
void remove();
// 设置默认缺省值
T initialValue();

二、ThreadLocal 的使用

2.1 实例一: ThreadLocal 返回默认值

public static void main(String[] args) {
    ThreadLocal<Integer> defaultValueLocal = new ThreadLocal() {
        public Integer initialValue() {
            return 1;
        }
    };

    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                final Integer result = defaultValueLocal.get();
                System.out.println(Thread.currentThread().getName()+ " --> result=" + result);
            }
        });
        thread.start();
    }
}

控制台输出:

Thread-0 --> result=1
Thread-1 --> result=1
Thread-2 --> result=1
Thread-3 --> result=1
Thread-4 --> result=1
Thread-5 --> result=1
Thread-6 --> result=1
Thread-7 --> result=1
Thread-8 --> result=1
Thread-9 --> result=1

2.2 实例二:set 设置值

public static void main(String[] args) {
    ThreadLocal<String> nameLocal = new ThreadLocal<>();

    for (int i = 0; i < 10; i++) {
        int finalI = i;
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                nameLocal.set(Thread.currentThread().getName() + " -->" + finalI);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前线程输出:" + nameLocal.get());
            }
        });
        thread.start();
    }
}

控制台输出:

当前线程输出:Thread-2 -->2
当前线程输出:Thread-1 -->1
当前线程输出:Thread-0 -->0
当前线程输出:Thread-3 -->3
当前线程输出:Thread-9 -->9
当前线程输出:Thread-8 -->8
当前线程输出:Thread-7 -->7
当前线程输出:Thread-6 -->6
当前线程输出:Thread-5 -->5
当前线程输出:Thread-4 -->4

三、源码

3.1 set 方法

public void set(T value) {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    // 判断 ThreadLocalMap 对象是否存在
    if (map != null)
        // 存在,则设置值到 ThreadLocalMap 中
        map.set(this, value);
    else
        // 否则创建 ThreadLocalMap 对象
        createMap(t, value);
}

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

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

**ThreadLocalMap ** 对象是 ThreadLocal 的一个静态内部类,里面定义了一个 Entry 来保存数据,而且还是继承的弱引用。在 Entry 内部使用 ThreadLocal 作为key,使用我们设置的 value 作为 value:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {       
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}	

3.2 get方法

public T get() {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    // 判断 ThreadLocalMap 对象是否存在
    if (map != null) {
        // 获取当前线程存储变量
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 变量不为空时,将值返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // ThreadLocalMap 对象不存在或不存在指定KEY值时,返回默认值
    return setInitialValue();
}

// 设置初始默认值
private T setInitialValue() {
    // 调用初始默认值设置函数
    T value = initialValue();
    // 将初始默认值存储到 ThreadLocalMap 对象中
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

3.3 initialValue 方法

protected T initialValue() {
    // 默认返回 null,按需覆盖
    return null;
}

四、ThreadLocal 内存泄漏风险

在上面我们介绍 ThreadLocal 源码的时候,了解到 ThreadLocal 通过 ThreadLocalMap 对象存储值,它的 key 存储的是当前的 ThreadLocal 的引用,属于弱引用。当 ThreadLocal 被回收时,value 却还存在,就会造成内存泄漏。

解决办法: 在使用完 ThreadLocal 后,执行 remove 操作,避免出现内存溢出情况。

以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

源代码系列02——ThreadLocal源码分析(基础篇)

java中的ThreadLocal详解及示例代码

ThreadLocal介绍

MyBatis基础:使用java提供的ThreadLocal类优化代码

Java 单线程代码ThreadLocal串值问题

ThreadLocal