ThreadLocal的get方法、set方法,以及其内存泄露问题

ThreadLocal的设计思想

既然多线程访问同一个变量会造成线程安全的问题,那么创建出来一个变量,需要使用这个变量的线程将该变量拷贝一份,并且拷贝到每一个线程的变量是线程私有的,使得变量在线程之间隔离起来使用,避免了线程之间的交错使用数据造成的线程安全问题。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,它中间有一个内部类 Entry。

	// 将Entry的key设置为弱引用
	static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
		// 构造方法,ThreadLocal作为键,value作为值
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

ThreadLocalMap将Entry设置为弱引用,是为了防止内存泄露。

将key设置为弱引用,在下一次GC的时候,将弱引用指向的对象回收。在多线程中,假设多个线程使用同一个ThreadLocal类型的变量,也就是每个线程的ThreadLocalMap的其中一个Entry中的key使用的是同一个ThreadLocal类型的变量。

举例

三个线程中的每个线程的 ThreadLocalMap 的其中一个 Entry 中的 key 使用的是同一个 ThreadLocal 类型变量的地址。都指向了 ThreadLocal1 。

此时假设是强引用:多个线程依赖同一个 ThreadLocal1 ,此时 线程 1 的 ThreadLocal1 使用结束了想要释放内存,但是由于是强引用(因为还有其他线程还在指向 ThreadLocal1),这就导致了线程1 的持有 ThreadLocal1 的 Entry 占有的内存无法释放,导致了内存泄露,使用弱引用的时候,这种问题就可以解决。

ThreadLocal中还设计了Entry数组来存放多个Entry。

private Entry[] table;

使用ThreadLocal

下面是一段演示代码,main函数作为线程,对ThreadLocal进行操作。

package com.school.service;

public class Main {
    public static void main(String[] args) {
        ThreadLocal t = new ThreadLocal();
        t.set("jqz");
        t.set("zy");
        String s = (String)t.get();
        System.out.println(s);
    }
}

set方法

点进set方法,进去为下方代码,为其加上注释。

    // set方法,参数为要插入的值
	public void set(T value) {
        // 获取对当前执行线程对象的引用。
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

点进getMap(t)这个方法,代码如下

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

getMap方法参数为线程对象,可以看见返回值为ThreadLocal,点进去t.threadLocals;

可以看到

ThreadLocal.ThreadLocalMap threadLocals = null;

这是在Thread类里的,Thread类中内置了ThreadLocalMap这个类型的参数,可以发现每个线程都是有自己独立的ThreadLocalMap对象

ThreadLocal通过getMap方法获取线程对象的ThreadLocal。

继续向下看set方法。

    // set方法,参数为要插入的值
	public void set(T value) {
        // 获取对当前执行线程对象的引用
        Thread t = Thread.currentThread();
        // 获取线程中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果map里面已经插入过值了
        if (map != null)
            // 对map中再set进去当前传入的值,它的键为这个ThreadLocal对象
            map.set(this, value);
        // 如果map里面没有插入过值
        else
            // 创建新的ThreadLocalMap对象,插入值进去,它的键为这个ThreadLocal对象
            createMap(t, value);
    }

get方法

点进get方法,进去为下方代码,为其加上注释。

    // get方法,返回当前线程,在该ThreadLocal中的值
	public T get() {
        // 获取对当前执行线程对象的引用
        Thread t = Thread.currentThread();
         // 获取线程中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果ThreadLocalMap存在
        if (map != null) {
            // 通过ThreadLocal作为键,获取ThreadLocalMap的Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果Entry不为空
            if (e != null) {
                // 获取结果
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    private T setInitialValue() {
        // 将value设为空值
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 将控制设置进ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        // 放回空值
        return value;
    }

场景

如果有两个线程使用相同的ThreadLocal,访问值会相同吗?

如果相同的线程访问两个不同的ThreadLocal,访问值会相同吗?

只有相同对象访问相同的ThreadLocal,访问值才会相同。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值