# ThreadLocal详解

ThreadLocal:线程局部变量。线程局部变量会将共享变量拷贝一份到当前线程中，保证了变量初始值的共享，线程之间变量的隔离，以及单个线程内的数据共享。

### 使用ThreadLocal

以JDBC Connection为例，每个dao都需要一个Connection对象用于进行数据库操作。因为Dao在Spring中是单例的，所以属性Connection可能会被多个线程访问，但是Connection并不是线程安全的。使用ThreadLocal可以很好的解决问题，每个线程在使用Connection时，拷贝一份Connection对象，各自使用各自的，从而避免了共享变量引发的线程安全问题：

```java
public final class ConnectionUtil {

    private ConnectionUtil() {}

    private static final ThreadLocal<Connection> conn = new ThreadLocal<>();

    public static Connection getConn() {
        Connection con = conn.get();
        if (con == null) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                con = DriverManager.getConnection("url", "userName", "password");
                conn.set(con);
            } catch (ClassNotFoundException | SQLException e) {
                // ...
            }
        }
        return con;
    }
}
```

### ThreadLocal源码分析

ThreadLocal主要拥有以下几个方法，下面从这几个方法作为切入点分析tl的源码：

```java
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("字符串"); // 初始化ThreadLocal
tl.get(); // 获取变量
tl.remove(); // 使用完毕，清除变量，否则有可能造成内存泄漏
```

先看set方法：

```java
/*ThreadLocal*/
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 当前线程中的threadLocals，该对象是一个Map
    if (map != null)
        map.set(this, value); // 如果map不为空，那么就向map中添加一个key为threadLocal，value为变量值的元素
    else
        createMap(t, value); // 如果map不存在，则创建一个map对象
}

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
```

所以，在线程设置一个共享变量到ThreadLocal中时，会将变量设置到Thread对象的threadLocals（是一个map）属性中。

再来看get以及remove方法：

```java
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); // 获取指定key对应的Entry对象
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result; // 取到了立即返回
        }
    }
    return setInitialValue(); // 没取到初始化并返回null
}

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread()); // 从threadLocals中移除key为tl的元素
    if (m != null)
        m.remove(this);
}
```

所以，ThreadLocal变量本质上是将变量存储在当前线程Thread对象的thredLocals属性中的，此属性类型是ThreadLocal的内部类ThreadLocalMap，存放的变量key为threadLocal对象，set、get、remove本质上都是对这个map进行操作。

现在我们再看ThreadLocalMap类：

```java
static class ThreadLocalMap {
   // map 存储的元素类型， 他继承自WeakReference，是一个弱引用
   static class Entry extends WeakReference<ThreadLocal<?>> {
       Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
   }
   // ThreadLocalMap实际上是一个Entry[]数组
   private Entry[] table;

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
       table = new Entry[INITIAL_CAPACITY];
       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
       table[i] = new Entry(firstKey, firstValue);
       size = 1;
       setThreshold(INITIAL_CAPACITY);
   }

}
```

### ThreadLocal是如何保证数据隔离的

ThreadLocal的隔离特性完全依赖线程之间的不可见性。具体请参考JMM（Java内存模型）。

### ThreadLocal内存泄漏

### 参考

* [神一样的存在-ThreadLocal详解](https://www.cnblogs.com/dreamroute/p/5034726.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/java/bing-fa-bian-cheng/threadlocal-xiang-jie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
