Notes
  • Introduce
  • Go
    • Grammar
      • Basic
      • Goroutines & Channels
      • Test
    • System Library
      • Module
      • sync
      • context
      • net
    • Concurrency in Go
    • The Go Memory Model
    • Code Snippet
  • Rust
    • The Rust Programming Language
    • Rust by Example
  • JAVA
    • Preface
    • Grammar
      • Basic
      • Data Types
      • Operator
      • Exceptions
    • Class Libraries
      • Collection
      • Stream
      • IO
      • NIO
      • RMI
    • Concurrency
      • Preface
      • JMM
      • Synchronized & CAS
      • Deadlock
      • Thread
      • Lock & Condition
      • Utility Class
      • Thread-safe Collection
      • Atomic Class
      • Fork/Join
      • Concurrency Design Patterns
        • Immutable
        • Copy-on-Write
        • ThreadLocal
        • Multitheading If
        • Division
    • JVM
      • Class & Instance Initialization
      • Runtime Data Area
      • Garbage Collection
    • Web Container
      • Tomcat Architecture
      • Jetty Architecture
    • Spring
    • Tuning
      • Programming
  • Computer Science
    • Computer Organization
    • Algorithm
      • Complexity
      • Linear List
      • Sort
      • Binary Search
      • Skip List
      • Hash Table
      • Tree
      • Graph
      • String Matching
      • Bloom Filter
      • Greedy Algorithm
      • Divide and Conquer
      • Back Tracking
      • Dynamic Programming
    • Network Protocol
      • Pysical Layer
      • Data Link Layer
      • Network Layer
      • Transport Layer
      • Application layer
      • HTTP
      • HTTP/2 in Action
    • Operating System
      • Basic
      • System Initialization
      • Diagnostic Tools
      • CPU Diagnosis
      • Memory Diagnosis
      • Disk Diagnosis
      • Network Diagnosis
      • Monitor System
    • Design Patterns
      • UML
      • OOP
      • Principle
      • Refactoring & Specification
      • Creational
        • Singleton
        • Factory
        • Builder
        • Prototype
      • Structural
        • Proxy
        • Bridge
        • Decorator
        • Adapter
        • Facade
        • Composite
        • FlyWeight
      • Behavioral
        • Observer
        • Template Method
        • Strategy
        • State
        • Iterator
        • Chain of Responsibility
    • Distributed System
      • Protocol & Algorithm
      • Transcation
      • Theory
      • Resource Management
      • Scheduling
      • Computing
      • Message Queue
      • Cache
      • Consistent Hashing
  • database
    • InfluxDB
      • In-Memory Index
      • Meta
    • MySQL
      • SQL
      • Architecture
      • Log
      • Transaction
      • Indexing
      • Lock
      • Storage
    • Redis
    • Elasticsearch
      • Local Debug
    • HBase
    • Kafka
    • ZooKeeper
  • Reading
    • RocketMQ
    • 演说之禅
    • So Good They Can't Ignore You
    • 学会提问
    • Lecture
  • Other
    • v2ray
    • Kubernetes
    • Git
    • Maven
    • Anaconda And Conda
    • Fuck! Shit!
      • Remove Final by Reflection
      • Ingress Host
      • ExecuterService submit
  • Open source contribution
Powered by GitBook
On this page
  • 使用方法
  • 原理实现
  • 注意事项
  • 内存泄漏
  • InheritableThreadLocal

Was this helpful?

  1. JAVA
  2. Concurrency
  3. Concurrency Design Patterns

ThreadLocal

PreviousCopy-on-WriteNextMultitheading If

Last updated 6 years ago

Was this helpful?

再次重复一遍,并发问题出现的根源是多个线程同时读写同一共享变量。通过 的方式可以只读不写,还有另一种方式就是:避免共享。

比如局部变量是线程安全的,因为每个线程都有自己的一份,没有共享,即线程封闭。Java 提供 ThreadLocal 也能做到避免共享,即每个线程都有自己的副本。

使用方法

我们知道 SimpleDateFormat 不是线程安全的,如果要在并发场景下使用它,有一个办法就是通过 ThreadLocal。

如果是同一个线程调用 get 方法,则会返回相同的实例,若是不同的线程调用 get 方法,则会返回不同的实例。

// java.lang.Thread.java
public class SafeDateFormat {
    private static final ThreadLocal<SimpleDateFormat> th = 
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static DateFormat get() {
        return th.get();
    }
}

原理实现

需要实现 ThreadLocal 的逻辑,我们有两个思路:

  • 在 ThreadLocal 中持有一个 Map,key 是线程 ID,value 是我们需要的值。

  • 在 Thread 中持有一个 Map,key 是 ThreadLocal,value 是我们需要的值。

Java 使用的是第二种思路,理由是:

  • 数据亲缘性:ThreadLocal 只是一个工具类,内部不应该存储与线程相关的数据,与线程相关的数据应该放在 Thread 里面。

  • 不容易产生内存泄漏:ThreadLocal 的生命周期往往比线程长,若数据放在 ThreadLocal中,就算 Thread 已经销毁了,相关的数据也不会被回收;若数据放在 Thread 中,Thread 对象可以被回收,那么 ThreadLocalMap 也可以被回收了。

下面是 Java 1.11 精简后的代码:

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    public Thread() {
        // inheritableThreadLocals 的逻辑
        // 若创建线程的线程存在 inheritableThreadLocals,则通过复制继承
        Thread parent = currentThread();
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
}
// java.lang.ThreadLocal.java
public class ThreadLocal<T> {
    static class ThreadLocalMap {
    
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        
        private Entry[] table;
        
        private Entry getEntry(ThreadLocal<?> key) {}
        private void set(ThreadLocal<?> key, Object value) {}
    }
    
    public T get() {
        ThreadLocalMap map = getMap(Thread.currentThread());
        ...
    }
    
    public void set(T value) {
        ThreadLocalMap map = getMap(Thread.currentThread());
        ...
    }
    
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

注意事项

内存泄漏

上文提到 Java 设计成 Thread 持有 ThreadLocal 数据的原因之一是不容易产生内存泄漏,但是在某些情况下还是会有内存泄漏的问题。

比如在线程池中使用 ThreadLocal。线程池中的线程存活时间很长,往往与程序的生命周期一致,所以 Thread 持有的 ThreadLocalMap 一直不会被回收。尽管 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用的,只要 ThreadLocal 自己的生命周期结束就可以被回收,但是 Entry 中的 Value 是强引用,所以尽管 Value 的生命周期结束了,也不会被回收,导致内存泄漏。

解决方案:手动释放,比如通过 try-finally。

ThreadLocal tl;

tl.set(obj);
try {
    ...
} finally {
    tl.remove();
}

InheritableThreadLocal

ThreadLocal 创建的线程变量,子线程是无法继承的,也就是说你在线程 A 中创建了 ThreadLocal V,然后又在线程 A 中创建了线程 B,但是在线程 B 中无法访问到 V。

Java 提供了 InheritableThreadLocal 来支持这种需求。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
Immutable