9.jdk源码阅读之ThreadLocal

1. 写在前面

这篇文章我们一起来看下日常工作中使用比较多的ThreadLocal,我先抛出几个问题,大家一起思考下:

  1. ThreadLocal的主要作用是什么?
  2. ThreadLocal怎么使用?
  3. ThreadLocal 的底层实现原理是什么?
  4. ThreadLocal 如何避免内存泄漏?
  5. 在使用线程池时,ThreadLocal 有哪些注意事项?
  6. ThreadLocal 和 synchronized 的区别是什么?
  7. InheritableThreadLocal 是什么?它与 ThreadLocal 有什么区别?
  8. ThreadLocal 的 initialValue 方法有什么作用?
  9. ThreadLocal 的应用场景有哪些?

2. 作用

ThreadLocal 是 Java 中用于创建线程本地变量的类。每个线程都有自己独立的变量副本,互不干扰。主要作用如下:

  • 线程安全性:通过为每个线程提供独立的变量副本,避免了多线程并发访问同一个变量时的竞争问题。
  • 简化代码:在某些场景下,使用 ThreadLocal 可以避免复杂的同步机制,使代码更加简洁和易读。
  • 状态隔离:适用于需要在多个方法或类之间共享线程状态的场景,例如数据库连接、用户会话等。

3. 使用示例

以下是一个简单的示例,展示如何使用 ThreadLocal:

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        Runnable task = () -> {
            int value = threadLocalValue.get();
            value += 1;
            threadLocalValue.set(value);
            System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();
    }
}


在这个示例中,每个线程都有自己独立的 threadLocalValue 副本,互不干扰。

4. 底层实现原理

  • ThreadLocal类:ThreadLocal 类本身并不存储实际的变量值,而是通过 ThreadLocalMap 来存储
  • ThreadLocalMap类:每个线程内部都有一个 ThreadLocalMap 对象,用于存储所有的 ThreadLocal 变量。
  • Entry类:ThreadLocalMap 使用一个内部静态类 Entry 来存储键值对,其中键是 ThreadLocal 对象,值是实际的变量值。

4.1 主要方法

  • set(T value):将当前线程的 ThreadLocal 变量设置为指定的值。
  • get():获取当前线程的 ThreadLocal 变量的值。
  • remove():删除当前线程的 ThreadLocal 变量。

4.2 主要数据结构

4.2.1 ThreadLocal

public class ThreadLocal<T> {
    // 每个ThreadLocal对象都有一个唯一的threadLocalHashCode
    private final int threadLocalHashCode = nextHashCode();

    // 获取当前线程的ThreadLocalMap
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    // 设置当前线程的ThreadLocal变量
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

4.2.2 ThreadLocalMap

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

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

    private Entry[] table;

    // 获取Entry
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

    // 设置Entry
    private void set(ThreadLocal<?> key, Object value) {
        int i = key.threadLocalHashCode & (table.length - 1);
        for (Entry e = table[i];
             e != null;
             e = table[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            if (k == key) {
                e.value = value;
                return;
            }
        }
        table[i] = new Entry(key, value);
    }
}

5. ThreadLocal 如何避免内存泄漏?

ThreadLocalMap 使用 WeakReference 来引用 ThreadLocal 对象。当 ThreadLocal 对象被垃圾回收后,ThreadLocalMap 中的 Entry 仍然存在,可能导致内存泄漏。因此,建议在使用完 ThreadLocal 变量后调用 remove() 方法,以清除线程局部变量,避免内存泄漏。

6. ThreadLocal 和 synchronized 的区别是什么?

  • ThreadLocal:提供线程本地变量,每个线程都有自己的独立副本,不需要同步,适用于需要在多个方法或类之间共享线程状态的场景。
  • synchronized:提供同步机制,确保同一时间只有一个线程可以访问被同步的代码块或方法,适用于需要多个线程访问同一个共享资源的场景。

7. 如何实现一个简单的 ThreadLocal 类?

public class SimpleThreadLocal<T> {
    private Map<Thread, T> threadLocalMap = new ConcurrentHashMap<>();

    public void set(T value) {
        threadLocalMap.put(Thread.currentThread(), value);
    }

    public T get() {
        return threadLocalMap.get(Thread.currentThread());
    }

    public void remove() {
        threadLocalMap.remove(Thread.currentThread());
    }
}

这个简单实现使用 ConcurrentHashMap 存储每个线程的变量副本。

8. InheritableThreadLocal 是什么?它与 ThreadLocal 有什么区别?

InheritableThreadLocal 是 ThreadLocal 的一个子类,它允许子线程继承父线程的线程本地变量。与 ThreadLocal 不同,ThreadLocal 变量在创建子线程时不会被继承。

9. ThreadLocal 的 initialValue 方法有什么作用?

initialValue 方法返回当前线程的 ThreadLocal 变量的初始值。可以通过重写 initialValue 方法来提供线程本地变量的默认值。Java 8 引入了 withInitial 方法,可以更方便地设置初始值。

ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

8.jdk源码阅读之ConcurrentHashMap(下)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值