多个线程并发读写同一共享变量会存在一些问题,只要我们突破共享变量就不会有并发问题。除了使用局部变量外,Java语言提供的线程本地存储(ThreadLocal)就能解决多线程共享变量问题。
下面我们以并发场景下使用线程不安全的 SimpleDateFormat 为例。
static class SafeDateFormat { // 定义ThreadLocal变量 static final ThreadLocal tl = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); static DateFormat get() { return tl.get(); } } // 不同线程执行下面代码返回不同的DateFormat DateFormat df = SafeDateFormat.get();不同线程调用 SafeDateFormat.get() 将返回不同的 SimpleDateFormat 对象实例,由于不同线程的 SimpleDateFormat 相互隔离,所以是线程安全的。
如下图所示,Thread 持有一个 ThreadLocalMap 的引用,ThreadLocalMap 以 ThreadLocal 为 key,任意对象为 value。 部分源码如下:
class Thread { // 内部持有 ThreadLocalMap 的引用 ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals; } class ThreadLocal { public T get() { // 首先获取线程持有的 ThreadLocalMap ThreadLocalMap map = Thread.currentThread().threadLocals; // 在 ThreadLocalMap 中查找变量 Entry e = map.getEntry(this); return e.value; } static class ThreadLocalMap { // 内部是Entry数组而不是Map Entry[] table; // 根据 ThreadLocal 查找 Entry Entry getEntry(ThreadLocal key) { ... } // Entry定义 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; } } }通过 ThreadLocal 创建的线程变量,其子线程是无法继承的。也就是说你在线程中通过 ThreadLocal 创建了线程变量 V,而后该线程创建了子线程,你在子线程中是无法通过 ThreadLocal 来访问父线程中的线程变量 V 。
如果你需要子线程继承父线程的线程变量,那该怎么办呢?Java 提供了 InheritableThreadLocal,InheritableThreadLocal 是 ThreadLocal 子类,所以用法和 ThreadLocal 相同,但由于存在和 ThreadLocal 同样的内存泄漏问题,不建议使用。
如果需要使用,可以考虑使用阿里的 TransmittableThreadLocal 框架。