ThreadLocal的使用

1. ThreadLocal介绍

ThreadLocal顾名思义,就是线程的本地变量,只有当前线程可见,对其他线程来说是封闭且隔离的。每一个线程为自己本身创建ThreadLocal变量,只有当前线程可以访问,其他的线程不可以,从根源上避免了多个线程对共享资源的竞争问题,提高程序的执行效率。

2. ThreadLocal的基本使用

这里以创建两个线程的ThreadLocal为例子,来说明ThreadLocal的基本使用,相关代码如下:

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

    public static void print(String threadName) {
        System.out.println("线程名:" + threadName + " 线程变量:" + threadLocal.get());
        // 移除线程变量
        threadLocal.remove();
    }


    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("local1");
                print("线程1");
                System.out.println("after remove:" + threadLocal.get());
            }
        }, "线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("local2");
                print("线程2");
                System.out.println("after remove:" + threadLocal.get());
            }
        }, "线程2").start();

    }

程序结果如下:
在这里插入图片描述
可以看到每一个线程都获取到了本身的线程变量,线程之间相互不影响。

3. ThreadLocal的实现原理

ThreadLocal如何实现线程变量之间互相不影响的呢?很简单,每一个线程都保存一份变量副本即可,下面将从设置值到取值的整个过程来说明。
ThreadLocal的设置值的方法是set, 源码如下:

public void set(T value) {
		// 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
        // 不为空,直接将新值覆盖旧值
            map.set(this, value);
        else
        // 为空则进行初始化
            createMap(t, value);
    }
    
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
	ThreadLocal.ThreadLocalMap threadLocals = null;

getMap方法返回的是每一个线程的threadLocals属性,threadLocals属性为ThreadLocal.ThreadLocalMap类型,保存每一个线程的ThreadLocal变量,其初始化在map=null的情况下进行,执行 createMap(t, value)方法进行初始化。

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            // 通过hash运算计算出threadLocal的位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
     static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

ThreadMap中的Entry的key为弱引用类型(这也就是为什么TheadLocal会存在内存泄漏的原因,后面解释)。当我们要进行取值时,则执行如下get方法,将ThreadLocal中的Entry取出,如果不存在,则会进行清理操作。

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();
    }
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);
   }
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
    }        
 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;
    }

4. ThreadLocal的内存泄漏问题的理解

ThreadLocal变量间的引用关系如下图所示:
在这里插入图片描述
当我们的ThreadLocal引用变成null时,由于ThreadMap的生命周期和当前线程一样,当前线程不结束,系统不会进行垃圾回收,这就造成一个现象:Entry中的key=null, 而value却存在,但我们无法在获取到value的值,这就造成内存泄漏。虽然在get方法中,当我们获取不到key的值时,会执行getEntryAfterMiss进行垃圾清理,但如果我们就一直访问key存在的Entry,getEntryAfterMiss方法就无法执行,内存泄漏还是存在,最稳妥的方法就是我们每次用完后都执行Remove操作,将变量手动清理。

ThreadLocal是一个Java类,用于在多线程环境下保存线程本地变量的副本。通过创建ThreadLocal对象,每个线程都可以拥有自己独立的变量副本,互不干扰。 使用ThreadLocal的过程如下: 1. 创建一个ThreadLocal对象,可以指定泛型类型。 2. 在每个线程中,通过ThreadLocal对象的`get()`方法获取当前线程的变量副本。如果当前线程没有设置过变量值,则会使用默认值进行初始化。 3. 在每个线程中,通过ThreadLocal对象的`set(value)`方法设置当前线程的变量值。 4. 在每个线程中,通过ThreadLocal对象的`remove()`方法移除当前线程的变量副本。 需要注意的是,ThreadLocal对象的生命周期与Thread对象的生命周期一样长。当ThreadLocal对象被垃圾回收时,关联的变量副本也会被回收。 在ThreadLocal内部,使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocal的实例作为key,变量值作为value。ThreadLocalMap可以使用强引用或弱引用来引用ThreadLocal对象。如果使用强引用,当ThreadLocal对象被回收时,如果没有手动删除对应的变量副本,会导致内存泄漏。如果使用弱引用,当ThreadLocal对象被回收时,对应的变量副本也会被回收。 总结来说,ThreadLocal是一种线程本地变量,通过保存每个线程的变量副本,实现了多线程环境下的线程隔离。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ThreadLocal使用与原理](https://blog.youkuaiyun.com/qq_35190492/article/details/116431270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ThreadLocal使用详解](https://blog.youkuaiyun.com/LJJZJ/article/details/88763666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值