前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >何时该使用 ThreadLocal,它的工作原理是什么(面试必背)?

何时该使用 ThreadLocal,它的工作原理是什么(面试必背)?

作者头像
水货程序员
修改2018-12-03 16:27:09
1K0
修改2018-12-03 16:27:09
举报
文章被收录于专栏:javathings

何时该使用 ThreadLocal,它的工作原理是什么(面试必背)?

ThreadLocal 的概念,面试的时候容易被问到。它的概念很简单,从类的名字就可以知道,线程本地变量的意思。即该变量运行在线程中时,每个线程都独立拥有它而不和其他线程中的这个值相冲突,其功能就使得这个变量就属于当前线程,和其他线程无关。

ThreadLocal 方法提供泛型的版本, 即该变量中存放的类型可以自己指定。声明该变量的时候,直接通过 new 方法就可以了。

下面的代码演示了如何声明该变量,同时对其初始化。

代码语言:javascript
复制
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override protected String initialValue() {
        return "";
    }
};

Java 8 中可以写成:

代码语言:javascript
复制
ThreadLocal<ArrayList<Long>> myThreadLocal = ThreadLocal.withInitial(ArrayList::new);

ThreadLocal 对值的读写很简单,分别是 get 方法和 set 方法。

代码语言:javascript
复制
myThreadLocal.set("Hello");
String threadLocalValue = myThreadLocal.get();

下面演示 ThreadLocal 的作用,先不使用 ThreadLocal。

代码语言:javascript
复制
class DemoThreadLocal {
	int num = 0;
	public void increase() {
		for (int i = 0; i < 5; i++) {
			num++;
			System.out.println(Thread.currentThread().getName() + " " + num);
		}
	}
}
 
public class Main {
	public static void main(String args[]) throws Exception {
		DemoThreadLocal d = new DemoThreadLocal();
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					d.increase();
				}
			}).start();
		}
	}
}

上面的代码循环创建 5 个线程,每个线程都使用了 DemoThreadLocal 的同一个实例,因此每次调用 increase,都会将实例的 num 自增 5 次,由于这个变量是共享的,所以,5 个线程调用的自增都会在同一个变量上。

运行结果如下,可以 看到 num 会被加到 25:

Thread-0 1 Thread-0 5 Thread-0 6 Thread-0 7 Thread-0 8 Thread-2 4 Thread-2 9 Thread-2 10 Thread-2 11 Thread-2 12 Thread-4 13 Thread-4 14 Thread-4 15 Thread-4 16 Thread-4 17 Thread-3 3 Thread-3 18 Thread-3 19 Thread-3 20 Thread-3 21 Thread-1 2 Thread-1 22 Thread-1 23 Thread-1 24 Thread-1 25

下面改下代码,演示使用 ThreadLocal 的情况。

代码语言:javascript
复制
class DemoThreadLocal {
	ThreadLocal<Integer> num = ThreadLocal.withInitial(() -> 0);
 
	public void increase() {
		for (int i = 0; i < 5; i++) {
			num.set(num.get() + 1);
			System.out.println(Thread.currentThread().getName() + " " + num.get());
		}
	}
}
 
public class Main {
	public static void main(String args[]) throws Exception {
		DemoThreadLocal d = new DemoThreadLocal();
 
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					d.increase();
				}
			}).start();
		}
	}
}

上面的代码使用了将 num 变量包入了 ThreadLocal 类,因此每个线程都独立拥有这个 num,每次调用 increase 时,线程各自的 num 自增 5 次,num 变量不是共享的。

运行结果如下:

Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-0 5 Thread-2 1 Thread-2 2 Thread-2 3 Thread-2 4 Thread-2 5 Thread-4 1 Thread-4 2 Thread-4 3 Thread-4 4 Thread-4 5 Thread-1 1 Thread-1 2 Thread-1 3 Thread-1 4 Thread-1 5 Thread-3 1 Thread-3 2 Thread-3 3 Thread-3 4 Thread-3 5


实际上,第一张未使用 ThreadLocal 的情况下,也可以让线程运行时,操作自己独立的 num。方法很简单,就是在线程运行的时候,把 DemoThreadLocal d = new DemoThreadLocal(); 这个变量作为线程自己局部变量。但是坏处就是每次线程运行,都需要实例化 DemoThreadLocal 类。

代码语言:javascript
复制
public class Main {
	public static void main(String args[]) throws Exception {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					<span style="background-color: #ffff00;">DemoThreadLocal d = new DemoThreadLocal();</span>
					d.increase();
				}
			}).start();
		}
	}
}

如果 DemoThreadLocal 需要设计成单例的对象,无法 new 出来,那么也就无法使用上面这种 new 的方法。这种情况下,想要让每个线程拥有各自的 num,也只能用 ThreadLocal 类。在某些场景下,由于使用 ThreadLocal 可以使得线程拥有各自独立的变量,从而避免使用 synchronized,使得代码简化。大家可以搜一下 ThreadLocal 解决 SimpleDateFormat 非线程安全的问题。

——————————–

ThreadLocal 原理

ThreadLocal 原理并不复杂,网上有很多文章分析的很透彻。

每个 Thread 类中有如下一行代码:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap 是 ThreadLocal 类中的内部静态类。每个线程都有一个 ThreadLocalMap 类变量,该类存放了键值对,其中键值对的 key 就是 ThreadLocal 的一个引用,value 就是 ThreadLocal 对应的值。ThreadLocal 实例本身并没有存储值,值都是存放在 ThreadLocalMap 中,ThreadLocal 的作用就是作为键值对中的一个 key。

借用网上一个图片,描述了这几个类之间的关系,Thread 包含一个 ThreadLocalMap,ThreadLocalMap 包含了键值对,键值对中把 ThreadLocal 作为 key。

图片来源于网络

简单的说就是每个线程中维护了一个大的键值对的表,用来存储线程本地变量的。

再背一下内存泄漏,可加分

ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,弱引用 ThreadLocal 被回收后,导致 ThreadLocalMap 中出现 key 为 null 的数据,这些数据的 value 值就会一直存在着引用链,导致无法 GC 回收,造成内存泄漏。所以使用完 ThreadLocal 后,最好调用 remove() 方法,清除数据。

参考:

https://stackoverflow.com/questions/1202444/how-is-javas-threadlocal-implemented-under-the-hood

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#ThreadLocal_u4E3A_u4EC0_u4E48_u4F1A_u5185_u5B58_u6CC4_u6F0F

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 何时该使用 ThreadLocal,它的工作原理是什么(面试必背)?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档