Threadlocal 对象的使用

本文详细介绍了Java中的ThreadLocal类,包括如何在不同线程间传递和获取对象,ThreadLocalMap的工作原理,以及潜在的内存泄漏问题。作者通过示例展示了ThreadLocal在多线程环境下的应用和注意事项。

ThreadLocalJava 提供的以一种方便的方式在一个线程内不同方法之间传递和获取对象的类。

使用

通过 set 方法可以为当前线程中的变量赋值

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello");

通过 get 方法获取变量的值

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello");
String value = threadLocal.get();

通过 withInitial 静态方法可以为变量设置初始化值

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "world");

通过继承 ThreadLocal 类并重写 initialValue 方法,可以在定义变量时为其赋初始值。

ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "world";
    }
};

示例代码如下:

public class ThreadLocalDemo {
    private static ThreadLocal<AtomicInteger> counter = ThreadLocal.withInitial(AtomicInteger::new);

    public static void main(String[] args) {

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                String threadName = Thread.currentThread().getName();
                for (int j = 0; j < 3; j++) {
                    increment();
                    System.out.printf("%s incremented value: %d, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, counter.get().get(), counter.hashCode(),counter.get().hashCode());
                }

            }, "thread-" + i).start();
        }
    }

    private static void increment() {
        counter.get().incrementAndGet();
    }
}

这个例子中,每个线程都有自己独立的 AtomicInteger 实例,并且可以通过 incrementAndGet() 方法对其进行原子递增操作。每个线程递增的值都是独立的,不会相互干扰。代码输入如下:

thread-0 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-4 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-4 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-3 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-1 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-1 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-2 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391
thread-1 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-3 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-4 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-0 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-3 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-2 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391
thread-0 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-2 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391

从输出中可以看出,不同线程访问的是用一个counter对象(counter.hashCode()结果相同),但是每个线程获得的 counter 对象实例不同(counter.get().hashCode()结果不同)。

原理

ThreadLocal 能在每个线程间进行隔离,其主要是靠在每个 Thread 对象中维护一个 ThreadLocalMap 来实现的。因为是线程中的对象,所以对其他线程不可见,从而达到隔离的目的。使用 Map 结构主要是因为一个线程中可能有多个 ThreadLocal 对象,这就需要一个集合来进行存储区分,而用 Map 可以更快地查找到相关的对象。

ThreadLocalMapThreadLocal 对象的一个静态内部类,内部维护一个 Entry 数组,实现类似 Mapgetput 等操作,为简单起见,可以将其看做是一个 Map,其中 keyThreadLocal 实例,valueThreadLocal 实例对象存储的值。

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;
	        }
	    }

set 和 get

public void set(T value) {
    Thread t = Thread.currentThread();
    // 当前线程的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    // this 指当前的 ThreadLocal 对象
        map.set(this, value);
    } else {
    // key 不存在,则创建 map 并设置值
        createMap(t, value);
    }
}
public T get() {
    Thread t = Thread.currentThread();
    // 当前线程的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    // this 指当前的 ThreadLocal 对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

其中 getMap 方法如下

ThreadLocalMap getMap(Thread t) {
	// threadLocals 是 Thread 中的一个变量,因此是线程隔离的,不会受其他线程影响
    // 其在 Thread 类中的定义如下:ThreadLocal.ThreadLocalMap threadLocals = null;
     return t.threadLocals;
 }

内存泄漏

根据上面Entry方法的源码,我们知道 ThreadLocalMap 使用 ThreadLocal 的弱引用作为 Key 的,而 value 却是一个强引用。

ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有外部强引用引用他,那么系统 gc 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现keynull 的 Entry,就没有办法访问这些 keynull 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 keynull 的 Entry 的 value 就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。


感谢大家读到这里,后续还会有其他相关文章,欢迎继续阅读。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值