大家好,我是栗子为。
又有好长时间没和大家见面了,这不,小为刚入职嘛,有很多要学要了解的东西,对于即将到来的任务,小为是既期待又担心,期待的是终于能把知识用在工作中了,担心的是如果让你写个并发的场景来解决某个问题,我真的能做到吗?
基于这些担心,小为决定今天和大家一起来看看在并发编程中常用的一个类,ThreadLocal,看看它是如何解决并发场景问题的,这也是面试中常考的,只要谈到高并发,就会问到ThreadLocal,今天就一起和大家拿下它,安排
在开始今天的内容前,小为整理了一下关于ThreadLocal比较经典的几个面试题,大家也可以试着先回答一下,回答不出来不要紧,看完今天的内容,这些问题就会如鱼得水
问题一:ThreadLocal中ThreadLocalMap的数据结构是什么?
问题二:ThreadLocal的key是什么引用,为什么?
问题三:ThreadLocal会有内存泄露的问题吗?
问题四:看过ThreadLocal源码吗?为什么最后会有一个remove方法
好了好了,大家回答得怎么样,别着急,下面都会有答案…
01
—
什么是ThreadLocal
本地线程?局部线程?
可以这么理解,ThreadLocal提供线程的局部变量。这些变量与正常的变量不同,对于每个线程在访问ThreadLocal实例的时候都会有自己独立的变量副本。实现了线程隔离,避免了线程安全问题
02
—
ThreadLocal实例
假设有一个资源类,它还有一个属性num,当有多个线程要对它进行增加操作时,我们通常采用的方式是加Synchronized锁,如下
// 资源类
public class ThreadLocalDemo {
int num = 0;
// 采用加锁的方式
public synchronized void addNum() {
++num;
}
}
这样存在一个问题,所有线程都是操作主内存中的变量,如果我想单独统计每个线程对它的操作,该如何做呢,这就需要用到ThreadLocal,给每个线程创建自己的局部变量,这样就可以统计每个线程操作的次数,那么我们如何为线程创建自己的局部变量呢,ThreadLocal给出了两种方式
// 方式一
ThreadLocal<Integer> numLocal1 = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
// 方式二,推荐使用,匿名内部类
ThreadLocal<Integer> numLocal2 = ThreadLocal.withInitial(() -> 0);
这两种方式都表明创建了一个线程局部变量,并赋予了初始值0,推荐使用第二种方式
这只是创建了局部变量,那如何定义操作,使得每次让numLocal这个变量+1呢
public void addNumByThreadLocal() {
numLocal2.set(numLocal2.get() + 1);
}
那我们在main函数里编写我们的业务逻辑,看看最终的效果吧
public static void main(String[] args) {
ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
int count = new Random().nextInt(5) + 1;
try {
for (int j = 0; j < count; j++) {
// 由于操作的是主内存上的变量,所以共享
threadLocalDemo.addNum();
// 由于每个线程独立拥有副本,可以用来统计每个线程分别操作了多少次
threadLocalDemo.addNumByThreadLocal();
}
System.out.println("线程" + Thread.currentThread().getName() + "调用了" + threadLocalDemo.numLocal2.get() + "次");
} finally {
// 为避免内存泄露问题,一定要remove,尤其在线程池中,会出现线程复用的问题,所以一定要remove
threadLocalDemo.numLocal2.remove();
}
}, String.valueOf(i)).start();
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "中此时num的值为:" + threadLocalDemo.num);
}
结果如下:
线程1调用了1次
线程4调用了1次
线程3调用了2次
线程5调用了5次
线程2调用了5次
main中此时num的值为:14
03
—
Thread、ThreadLocal、ThreadLocalMap的关系
我们一起来看看这些类的源码
Thread.class
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这也说明,对于每个线程,由ThreadLocal来维护自己的变量值
ThreadLocal.class
查看ThreadLocal的源码可以看到,ThreadLocal类有一个静态内部类ThreadLocalMap
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;
}
}
...
}
ThreadLocalMap
回答问题二:ThreadLocalMap里维护的是一个Entry,类似字典的一个结构,我们可以看到,键是ThreadLocal,而值是一个Object,同时Entry继承了一个弱引用,这个问题会在后面进行介绍
类图

回答问题一:threadLocalMap实际上是一个以threadLocal实例为key,任意对象为value的Entry对象
当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放
它们三者之间的关系总结一句话就是
对于每个Thread,都有ThreadLocal来维护自己线程的变量,ThreadLocal里面的静态内部类ThreadLocalMap用来存储变量值,键为threadLocal实例,值为任意对象
04
—
总结
好啦,今天小为就和大家分享到这,关于ThreadLocal剩余的内容,例如弱引用带来的问题、内存泄露等小为打算放到下一篇,总结一下,今天我们了解到了ThreadLocal的定义、Thread的使用方式以及常用的一些api还有Thread、ThreadLocal以及ThreadLocalMap三者的关系,那我们下期再见啦~~
关注六只栗子,面试不迷路。
作者 栗子为
编辑 一口栗子
原文始发于微信公众号(六只栗子):ThreadLocal看完你就明白了(一)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88363.html