Java中的ThreadLocal
关于 ThreadLocal,我们经常用它来解决多线程并发问题,那它究竟是如何做到的?今天就让我们来好好看一下。
从源码入手
首先,让我们看看 ThreadLocal 类中的介绍:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
按照文中所述,ThreadLocal 提供的是线程本地变量,每个线程都有一份单独的副本,经常使用的方式是私有静态变量。关键在于下一段,线程存活,ThreadLocal 实例就可以被访问,线程消失,就会被垃圾回收。
get()方法
看到这儿,有没有想起上一篇内容所说的引用类型,有可能是软引用或者弱引用,具体是什么呢?还是来看看代码:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程里的map
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
上面展示的是 ThreadLocal 中的get()方法,关键的 map 是在 Thread 类中的threadLocals变量,让我们继续看看 ThreadLocalMap 的源代码:
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// 使用ThreadLocal作为key,并且是弱引用
super(k);
value = v;
}
}
// 省略代码
}
根据上一篇文章所述,如果一个对象只有弱引用,那么当下一次 GC 进行时,该对象就会被回收。那么让我们整理一下:
- ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为
弱引用。 - ThreadLocal 本身并不存储值,具体的 value 依旧在各个线程中。因此你可以把 ThreadLocal 看成一个工具类。
但需要注意的是,Entry 中,只有key是弱引用,但 value 依旧是强引用。那会不会出现 key 被垃圾回收后,这个 map 的 key 为 null,但 value 依旧存在的情况呢?
set()方法
确实是有可能的,但 JDK 本身也做了优化,可以看看 ThreadLocalMap 的 set()方法:
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();
}
调用 set()的时候,ThreadLocalMap 检查到 key 为 null 的 entry 时,会将 value 也设置为 null,这样 value 之前对应的实例也可以被回收。
使用场景
简单使用
先让我们看一个简单的例子:
public class ThreadLocalSimpleDemo {
public static void main(String[] args) {
int threads = 3;
InnerClass innerClass = new InnerClass();
for (int i = 1; i <= threads; i++) {
new Thread(() -> {
for (int j = 0; j < 4; j++) {
innerClass.add(String.valueOf(j));
innerClass.print();
}
innerClass.set("hello world");
}, "thread - " + i).start();
}
}
private static class InnerClass {
/**
* 添加
*/
public void add(String newStr) {
StringBuilder str = Counter.counter.get();
Counter.counter.set(str.append(newStr));
}
/**
* 打印
*/
public void print() {
System.out.printf(
"Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString()
);
}
/**
* 赋值
*/
public void set(String words) {
Counter.counter.set(new StringBuilder(words));
System.out.printf(
"Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString()
);
}
}
private static class Counter {
/**
* 初始化时是一个空的StringBuilder对象
*/
private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(StringBuilder::new);
}
}
其打印结果为:
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:01
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0123
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:820066274, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:012
Set, Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:155293473, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:1804272849, Value:hello world
可以看出,我们在使用 ThreadLocal 时,用的是同一个对象,但各个线程对应的实例是不一样的。而在调用 set() 方法后,对应的实例会被替换。
Session
对于 Java Web 应用而言,Session 保存了很多信息。很多时候需要通过 Session 获取信息,有些时候又需要修改 Session 的信息。一方面,需要保证每个线程有自己单独的 Session 实例。另一方面,由于很多地方都需要操作 Session,存在多方法共享 Session 的需求。使用 ThreadLocal 进行实现:
public class SessionHandler {
public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public String getUser() {
return session.get().getUser();
}
public String getStatus() {
return session.get().getStatus();
}
public void setStatus(String status) {
session.get().setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
handler.getStatus();
handler.getUser();
handler.setStatus("close");
handler.getStatus();
}).start();
}
}
总结
ThreadLocal 使用起来虽然简单,但考虑到其设计确实很精巧,值得了解一下。
有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。


Java中的ThreadLocal的更多相关文章
- Java中的ThreadLocal深入理解
提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...
- 理解Java中的ThreadLocal
提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...
- Java中的ThreadLocal详解
一.ThreadLocal简介 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线 ...
- 谈谈Java中的ThreadLocal
什么是ThreadLocal ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本.通过ThreadLocal可以将对象的 ...
- 理解java中的ThreadLocal(转)
一.对ThreadLocal概述 JDK API 写道: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的 ...
- 理解java中的ThreadLocal 专题
ThreadLocal每一印象: public class IncrementWithStaticVariable{ private static int seqNum = 0; public int ...
- java 中的 ThreadLocal
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各 ...
- Java中的ThreadLocal使用
ThreadLocal用于下面的场景: 1. 不允许多个线程同时访问的资源 2. 单个线程存活过程只使用一个实例 官方定义如下: This class provides thread-local va ...
- Java ThreadLocal Example(java中的ThreadLocal例子)
Java ThreadLocal is used to create thread local variables. We know that all threads of an Object sha ...
随机推荐
- 九、ITK-VTK混合编程--将序列dcm图像通过ITK读去并通过VTK显示出来
一.初步介绍 上一个博客里面我记录了VTK的安装,以及相关的工程代码的构建,但是实际上上一个博客测试的代码的例子仅仅只是VTK程序的例子.而我实际上希望能够实现的是VTK和ITK混合编程. 在这里还是 ...
- 【安富莱】V6,V5开发板用户手册,重在BSP驱动包设计方法,HAL库的框架学习,授人以渔(2019-11-04)
说明: 1.本教程重在BSP驱动包设计方法和HAL库的框架学习,并将HAL库里面的各种弯弯绕捋顺,从而方便我们的程序设计. 2.本次工程延续以往的代码风格,从底层BSP驱动包到应用代码,变量命名,文件 ...
- 转载-C语言中<<、>>、&、|的实际用途
C语言中<<.>>.&.|的实际用途 作为一个开发人员,在看别人项目或者看第三方库的源代码时,可能经常会看到譬如a>>4&0x0f这样的写法,对于一 ...
- IT兄弟连 HTML5教程 HTML5表单 HTML表单设计2
5 隐藏域 隐藏域不会在表单中显示.如果需要在页面之间传递重要数据,则在<input>标签中设置type属性值为“hidden”建立一个隐藏域.name和value属性是必需的,用来表示 ...
- SpringBoot源码学习系列之@PropertySource不支持yaml读取原因
然后,为什么@PropertySource注解默认不支持?可以简单跟一下源码 @PropertySource源码: 根据注释,默认使用DefaultPropertySourceFactory类作为资源 ...
- Java中的String为什么要设计成不可变的?
一.不可变类和不可变对象 Normally,you create an object and allow its contents to be changed later.However ,occas ...
- python进程基础点整理
操作系统 串行: 一个程序完完整整的执行完再执行下一个 并发: 看起来像是同时运行,其实就是程序间的切换频率比较快,看不出来 并行:真正的同时运行 多道技术 空间复用:共用一个内存条,多个进程相互隔离 ...
- 教你用Python实现免费蹭WiFi,只要有WiFi的地方,你就不会断网!
想象一下,程序员没有WIFI会怎样? 程序员没有网络肯定会寸步难行! 但是对于Python程序员来说,只要附近有热点,分分钟就能蹭网成功! 想要WIFI破解,python+字典,这是必少不了的.热点加 ...
- DevExpress的进度条控件ProgressBarControl的使用-以ZedGraph添加曲线进度为例
场景 Winform控件-DevExpress18下载安装注册以及在VS中使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1 ...
- Implement Dependent Reference Properties 实现从属引用属性 (XPO)
In this lesson, you will learn how to implement properties whose values can depend on other properti ...