多线程与高并发(六):ThreadLocal

一、什么是ThreadLocal:

从字面上解释,ThreadLocal即为线程本地变量,意思是ThreadLocal中的变量属于当前线程,该变量对其他线程是隔离的。

在多线程时,为了保证线程安全,通常会使用加锁的方式对共享变量进行访问,但除了这种方式之外,还可以用ThreadLocal,通过线程所使用到的变量对其他线程隔离来保证线程安全。

 

二、ThreadLocal的使用:

public class ThreadLocalTest {
    // new一个threadLocal对象tl
    static ThreadLocal<String> tl = new ThreadLocal<>();
    public static void main(String[] args) {

        // 在线程thread1中,往tl中放入"hello",并打印tl中的对象
        new Thread(() -> {
            tl.set("hello");
            System.out.println(Thread.currentThread().getName() + tl.get());
        }, "thread1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在线程thread2中,尝试取出tl中的对象并打印
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + tl.get());
        }, "thread2").start();
    }
}

输出:

thread1hello
thread2null

 

可以看到,在threadlocal中,thread1放入的对象thread2并不能访问到。

 

三、ThreadLocal实现原理:

1、从set方法看起

首先会拿到当前线程,调用getMap方法拿到一个当前线程的ThreadLocalMap,如果map不为空,threadlocal作为key,将值set到map中,否则创建一个ThreadLocalMap并将值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);
}

2、getMap(Thread)

从getMap中可以看到,返回的是线程自己本身的成员变量threadLocals,即set中所用到的map是当前线程自己的对象。

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

 

四、ThreadLocal结构:

在Thread中维护着一个threadLocalMap对象,它里面的key为threadLocal,value为threadLocal中set的值。

 

五、ThreadLocalMap如何解决hash冲突:

static class ThreadLocalMap {

	private static final int INITIAL_CAPACITY = 16;

	private Entry[] table;
}

ThreadLocalMap中维护着一个Entry数组,用于储存key、value,ThreadLocalMap是通过线性探测法去找到Entry的特定位置进行存储的。

线性探测法:首先会对threadLocal对象的hash进行一些计算,然后对数组长度取模,找到数组的某个位置进行存储,如果该位置已经被占据,则index+1继续寻找,直到找到空闲位置为止,若index+1>length-1,则将index置为0。

 

六、如何避免内存泄露:

内存泄露:有些对象不会被使用,但永远不会被JVM回收。

1、如何避免ThreadLocalMap中key的内存泄露:

由于ThreadLocal对象被线程里的ThreadLocalMap中的key指向,就算不在其它地方使用到此ThreadLocal对象,它也永远不会被回收。真是这样吗?

通过源码可以发现,Entry继承于一个弱引用对象,它的key是被一个弱引用指向的。

首先解释什么是弱引用:每次GC时,如果发现一个对象是只由弱引用指向的对象,会被立即回收。

所以当线程的其它地方不再使用到此ThreadLocal对象后,每次GC时,就会把此ThreadLocal对象回收,不会造成内存泄露。

static class Entry extends WeakReference<ThreadLocal<?>> {
	/** The value associated with this ThreadLocal. */
	Object value;

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

2、如何避免ThreadLocalMap中的value的内存泄露

由于指向value的引用为强引用,所以就算其它地方不再使用到value时(key被回收),它也可能不会自动被回收调,所以需要主动调用threadLocal.remove方法主动将ThreadLocalMap中对应的value置为null,GC时才会回收value对象。

注:

由于ThreadLocal是通过线性探测法解决hash冲突,在get使用线性探测法的过程中,会找到key为null的entry,顺便会将它们的value置为null。

ThreadLocal在扩容时,会将不再使用到的value置为null。

 

七、ThreadLocal应用场景:

有些线程内共享变量,可以考虑保存在ThreadLocal中,避免每次都需要增加方法参数进行传递。例如:

不同的线程使用不同的Session时。

当不同的线程使用不同的数据库连接时。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值