ThreadLocal的用处
TheadLocal的应用场景有哪些
使用TheadLocal会有哪些疑惑
TheadLocal的使用示例
在阅读源码框架的时候经常会看到框架里面使用ThreadLocal这个类,那么这个类具体是用来干什么的?他的主要应用场景有哪些呢?ThreadLocal的一些问题疑惑?包括其中内部的源码是怎么实现呢?
那么这篇文章主要就是针对这些问题做一个总结
TheadLocal是用什么做什么的?
通过源码注释的介绍来理解一下
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code 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).
这个类主要是提供一个线程的本地变量,这些变量的副本在每个线程中都是不一样的,他们主要通过他们自己的get和set方法来获取或者修改参数,相互独立的初始化复制这些参数,通常都是ThreadLocal的实例
简单来说就是每个线程通过get方法都是获得一个初始化的对象,这个对象被这个对象独自持有并且可以在线程的任何地方去获取和修改
TheadLocal的应用场景有哪些
TheadLocal的引用场景非常广泛
-
比如常见的有spring-transaction中的使用,在事务传递的过程就是通过TheadLocal把transaction传递到多个方法中去确定事务的开始和结束,有了TheadLocal就不需要在多个方法中通过传递事务对象来传递事务
-
还比如在seata分布式事务框架中,就会通过TheadLocal来传递xid(全局事务id)
-
在比如在使用SimpleDateFormat的过程中,由于SimpleDateFormat是一个线程非安全的类,所以在多个线程同时使用同一个SimpleDateFormat的时候可能会造成时间错乱的问题,是时候可以通过ThredLocal来关联SimpleDateFormat让每个线程中独立持有SimpleDateFormat,解决多线程调用SimpleDateFormat的问题
(有人可能会用创建多个对象是否会造成内存浪费,这个在TheadLocal源码中早就考虑这个问题,使用WeakMap来维护线程和实例的映射关系,在内存不够的时候会主动释放掉这部分内存,无需使用者去关心这个问题)
TheadLocal会有哪些疑惑?
-
如果ThreadLocal对象被置为null,线程中对应的ThreadLocal对象是否还存在?
其实这个在TheadLocal源码中早就考虑这个问题,使用WeakRefernce的Entry来维护线程和实例的映射关系,在ThreadLocal为null的时候,会自动回收线程中持有的ThreadLocal对象
关于弱引用和强引用的区别可以参考
java中的四种引用方式区别 -
在线程使用的过程中,如果希望父类线程的值可以传递到子类线程中,实现子类线程可以获取父类线程的值,那么TheadLocal是否可以做到对象传递?
原本的TheadLocal是做不到这点的,但是可以通过InheritableThreadLocal这个TheadLocal的继承类来实现这一点,其中就是通过把映射map放到inheritableThreadLocals中来实现值的传递,后面会有详细的源码解析来讲述这个问题
TheadLocal的使用示例
首先介绍一下TheadLocal的使用示例,后面后跟着InheritableThreadLocal的使用方法,其实使用是一样的,主要是看一下使用的效果
代码如下
package blog;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadLocal {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
public static int get() {
return threadId.get();
}
private static Thread newTestThread(Integer id) {
return new Thread(() -> {
System.out.println("parent myThread id is " + id + ",value is " + MyThreadLocal.get());
threadId.set(MyThreadLocal.get() + 1000);
System.out.println("parent myThread id is " + id + ",value is " + MyThreadLocal.get());
Thread child = new Thread(() -> {
System.out.println("child myThread id is " + id + ",value is " + MyThreadLocal.get());
threadId.set(MyThreadLocal.get() + 1000);
System.out.println("child myThread id is " + id + ",value is " + MyThreadLocal.get());
});
child.start();
try {
child.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
private static void testMutipleThread(int threadCount) {
//定义多个线程
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = newTestThread(i);
}
//分别开始多个线程
for (Thread t : threads) {
t.start();
}
//主线程等待其他线程执行结束
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
testMutipleThread(3);
System.out.println("多个线程执行结束");
}
}
返回结果
通过上面样例可以看到,每个线程在通过TheadLocal.get()方法获取了初始化的Integer对象并且每个对象是由每个线程独立持有。
(通过上面的返回值可以看到父类线程和子类线程持有的值是不共享的)
那下面就使用InheritableThreadLocal来看看效果
package blog;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadLocal {
private static final AtomicInteger nextId = new AtomicInteger(0);
// private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
private static final InheritableThreadLocal<Integer> threadId = new InheritableThreadLocal<Integer>() {
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int get() {
return threadId.get();
}
private static Thread newTestThread(Integer id) {
return new Thread(() -> {
System.out.println("parent myThread id is " + id + ",value is " + MyThreadLocal.get());
threadId.set(MyThreadLocal.get() + 1000);
System.out.println("parent myThread id is " + id + ",value is " + MyThreadLocal.get());
Thread child = new Thread(() -> {
System.out.println("child myThread id is " + id + ",value is " + MyThreadLocal.get());
threadId.set(MyThreadLocal.get() + 1000);
System.out.println("child myThread id is " + id + ",value is " + MyThreadLocal.get());
});
child.start();
try {
child.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
private static void testMutipleThread(int threadCount) {
//定义多个线程
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = newTestThread(i);
}
//分别开始多个线程
for (Thread t : threads) {
t.start();
}
//主线程等待其他线程执行结束
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
testMutipleThread(3);
System.out.println("多个线程执行结束");
}
}
这里和上面代码不太相同的是
ThreadLocal对象定义那一块,同时TheadLocal中有withInitial来通过匿名对象来实现initialValue方法值的获取,但是由于withInitial中返回的是SuppliedThreadLocal,这个类只是通过构造器的传入封装了一个匿名函数来实现initialValue,如果在初始化InheritableThreadLocal对象的时候如果还是使用withInitial这个方法则是不行的,因为返回的还是SuppliedThreadLocal对象,这个对象是对ThreadLocal对象的一个封装,这样将不会调用到InheritableThreadLocal的重载方法将会导致父子类线程的值传递失去效果。
最终结果如下
从这个结果看到和之前的结果已经不太一样的,在子类线程最开始打印出的对象值是和父类线程中对象的值相同的,所以由此可见这个对象在父子类对象中进行了传递。
这里篇幅太长了,后续会在下一篇文章将讲述ThreadLocal是如何实现这些功能的。