文章目录
多个线程同时读写同一共享变量存在并发问题, 避免共享,就能避免并发问题。
1. ThreadLocal的使用方法
static class ThreadId {
static final AtomicLong nextId = new AtomicLong(0);
// 定义ThreadLocal变量
static final ThreadLocal<Long> tl =
ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// 此方法会为每个线程分配一个唯一的Id
static long get() {
return tl.get();
}
}
- 一个线程前后两次调用ThreadId的get()方法,两次的返回值是相同的;
- 两个线程分别调用ThreadId的get()方法,那么两个线程看到的get()方法的返回值是不同的。
SimpleDateFormat不是线程安全的,在并发场景下可以用ThreadLocal解决线程安全问题。不同线程并不共享SimpleDateFormat,就像局部变量一样,所以线程安全。
static class SafeDateFormat {
// 定义ThreadLocal变量
static final ThreadLocal<DateFormat> tl = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
static DateFormat get() {
return tl.get();
}
}
2. ThreadLocal的工作原理
2.1 自己实现的ThreadLocal
class MyThreadLocal<T> {
Map<Thread, T> locals = new ConcurrentHashMap<>();
// 获取线程变量
T get() {
return locals.get(Thread.currentThread());
}
// 设置线程变量
void set(T t) {
locals.put(Thread.currentThread(), t);
}
}
直接创建一个Map,线程为key,线程拥有的变量为value。
2.2 java实现ThreadLocal
class Thread {
// 内部持有ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals;
}
class ThreadLocal<T> {
public T get() {
// 首先获取线程持有的
// ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
// 在ThreadLocalMap中
// 查找变量
Entry e = map.getEntry(this);
return e.value;
}
static class ThreadLocalMap {
// 内部是数组而不是Map
Entry[] table;
// 根据ThreadLocal查找Entry
Entry getEntry(ThreadLocal key) {
// 省略查找逻辑
}
// Entry定义
static class Entry extends WeakReference<ThreadLocal> {
Object value;
}
}
}
Java的实现里面也有一个Map,叫做ThreadLocalMap,不过持有ThreadLocalMap的不是ThreadLocal,而是Thread。Thread这个类内部有一个私有属性threadLocals,其类型就是ThreadLocalMap,ThreadLocalMap的Key是ThreadLocal。
我们的设计里面Map属于ThreadLocal,而Java的实现里面ThreadLocalMap则是属于Thread。这两种方式哪种更合理呢?很显然Java的实现更合理一些。在Java的实现方案里面,ThreadLocal仅仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在Thread里面,这样的设计容易理解。而从数据的亲缘性上来讲,ThreadLocalMap属于Thread也更加合理。
还有一个原因,不容易产生内存泄露. 在我们的设计方案中,ThreadLocal持有的Map会持有Thread对象的引用,这就意味着,只要ThreadLocal对象存在,那么Map中的Thread对象就永远不会被回收。ThreadLocal的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄露。而Java的实现中Thread持有ThreadLocalMap,而且ThreadLocalMap里对ThreadLocal的引用还是弱引用(WeakReference),所以只要Thread对象可以被回收,那么ThreadLocalMap就能被回收。Java的这种实现方案虽然看上去复杂一些,但是更加安全。
3. ThreadLocal与内存泄露
在线程池中使用ThreadLocal可能导致内存泄露,
// TODO 作者写的不是很好理解,待补
4. InheritableThreadLocal与继承性
不建议使用。
5. 总结
线程本地存储模式本质上是一种避免共享的方案。
6. 课后思考
实际工作中,有很多平台型的技术方案都是采用ThreadLocal来传递一些上下文信息,例如Spring使用ThreadLocal来传递事务信息。我们曾经说过,异步编程已经很成熟了,那你觉得在异步场景中,是否可以使用Spring的事务管理器呢?