4-线程共享ThreadLocal

1、ThreadLocal

利用threadlocal主要是为了达到线程隔离,实现线程安全
threadlocal用了hashmap的思想,key是固定的:就是当前线程

一、 ThreadLocal

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 线程上的一个值。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

如比我们初始化一个ThreadLocal变量 = 0

    //可以理解为 一个map,类型 Map<Thread,Integer>
    //实际上thread底层就是这么实现的
	static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){
		@Override
		protected Integer initialValue() {
			return 0;
		}
	};

启3个线程,A B C ;线程A 对ThreadLocal 加 1 一定得到1
线程B 对ThreadLocal 加 2 一定得到2
线程B 对ThreadLocal 加 3 一定得到3
每个线程对 ThreadLocal 变量的操作,其它线程影响不了。
ThreadLocal底层实际是由一个map实现线的,Key为当前线程,value就是初始化的值。
所以每个线程取ThreadLocal 值时就是通过value = map.get(thread)
获取到每个线程自己的变量 进行操作。

二、 ThreadLocal常见用法

ThreadLocal常用于线程池,每个线程都需要有一个自己的链接,用ThreadLocal保存,那么每个线程互不影响

三、threadLocal get set 方法

在这里插入图片描述

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

//set

public void set(T value) {
    Thread t = Thread.currentThread();
    //获取当前线程的成员变量ThreadLocalMap,不同的线程获取到不同对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //当前threadLocal对象,和value
        map.set(this, value);
    else
        createMap(t, value);
}


private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //根据threadLocal对象hashCode,定位entry数据中的位置
    //同一个线程中定义的不同threadLocal对象会定位到不同的位置
    //key.threadLocalHashCode得到这个对象的hashcode值,
    //这个值是一个final int类型在new ThreadLocal对象的时候初始化好的
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            //有值覆盖
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
     //无值在定位到的位置上新增一个对象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
//key.threadLocalHashCode:定义
//通过自旋cas的方式获取hashcode,
//每个new出来的threadLocal对象hashcode值不一样
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
   return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public final int getAndAdd(int delta) {
   return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
   do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

//get

public T get() {
    Thread t = Thread.currentThread();
    //获取当前线程的成员变量ThreadLocalMap,不同的线程获取到不同对象
    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();
}

1.1、ThreadLoalMap

ThreadLoalMap它也是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。

在ThreadLoalMap中,是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。

1.1.1、 hash冲突

private void set(ThreadLocal<?> key, Object value) {
    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
  • 1、如果当前位置是空的,就初始化一个Entry对象放在位置i上;
  • 2、位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
  • 3、位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;

1.1.2、内存泄漏

原因:ThreadLocalMap的实现中,key被保存到了WeakReference对象中。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
java有4种引用类型
强引用 GC也不会被回收
软引用 内存不够才会被回收
弱引用 只能存活到下次GC
虚引用 随时可能被回收

1.1.3、如何避免内存泄露

在调用ThreadLocal的get()、set()清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。

try {
    localName.set("test");   
// 其它业务逻辑
} finally {
    localName.remove();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值