Java基础进阶之ThreadLocal详解

7次阅读

共计 3161 个字符,预计需要花费 8 分钟才能阅读完成。

ThreadLocal 基本在项目开发中基本不会用到, 但是面试官是比较喜欢问这类问题的; 所以还是有必要了解一下该类的功能与原理的.
ThreadLocal 是什么
ThreadLocal 是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用 ThreadLocal 来维护变量时, ThreadLocal 会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况;
ThreadLocal 类用在哪些场景
一般来说, ThreadLocal 在实际工业生产中并不常见, 但是在很多框架中使用却能够解决一些框架问题; 比如 Spring 中的事务、Spring 中 作用域 Scope 为 Request 的 Bean 使用 ThreadLocal 来解决.
ThreadLocal 使用方法
1、将需要被多线程访问的属性使用 ThreadLocal 变量来定义; 下面以网上多数举例的 DBConnectionFactory 类为例来举例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnectionFactory {

private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection(“”, “”, “”);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};

public Connection getConnection() {
return dbConnectionLocal.get();
}
}
这样在 Client 获取 Connection 的时候, 每个线程获取到的 Connection 都是该线程独有的, 做到 Connection 的线程隔离; 所以并不存在线程安全问题
ThreadLocal 如何实现线程隔离
1、主要是用到了 Thread 对象中的一个 ThreadLocalMap 类型的变量 threadLocals, 负责存储当前线程的关于 Connection 的对象, 以 dbConnectionLocal 这个变量为 Key, 以新建的 Connection 对象为 Value; 这样的话, 线程第一次读取的时候如果不存在就会调用 ThreadLocal 的 initialValue 方法创建一个 Connection 对象并且返回;
具体关于为线程分配变量副本的代码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
1、首先获取当前线程对象 t, 然后从线程 t 中获取到 ThreadLocalMap 的成员属性 threadLocals
2、如果当前线程的 threadLocals 已经初始化(即不为 null) 并且存在以当前 ThreadLocal 对象为 Key 的值, 则直接返回当前线程要获取的对象(本例中为 Connection);
3、如果当前线程的 threadLocals 已经初始化 (即不为 null) 但是不存在以当前 ThreadLocal 对象为 Key 的的对象, 那么重新创建一个 Connection 对象, 并且添加到当前线程的 threadLocals Map 中, 并返回
4、如果当前线程的 threadLocals 属性还没有被初始化, 则重新创建一个 ThreadLocalMap 对象, 并且创建一个 Connection 对象并添加到 ThreadLocalMap 对象中并返回。
如果存在则直接返回很好理解, 那么对于如何初始化的代码又是怎样的呢?
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
1、首先调用我们上面写的重载过后的 initialValue 方法, 产生一个 Connection 对象
2、继续查看当前线程的 threadLocals 是不是空的, 如果 ThreadLocalMap 已被初始化, 那么直接将产生的对象添加到 ThreadLocalMap 中, 如果没有初始化, 则创建并添加对象到其中;
同时, ThreadLocal 还提供了直接操作 Thread 对象中的 threadLocals 的方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这样我们也可以不实现 initialValue, 将初始化工作放到 DBConnectionFactory 的 getConnection 方法中:
public Connection getConnection() {
Connection connection = dbConnectionLocal.get();
if (connection == null) {
try {
connection = DriverManager.getConnection(“”, “”, “”);
dbConnectionLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
那么我们看过代码之后就很清晰的知道了为什么 ThreadLocal 能够实现变量的多线程隔离了; 其实就是用了 Map 的数据结构给当前线程缓存了, 要使用的时候就从本线程的 threadLocals 对象中获取就可以了, key 就是当前线程;
当然了在当前线程下获取当前线程里面的 Map 里面的对象并操作肯定没有线程并发问题了, 当然能做到变量的线程间隔离了;
现在我们知道了 ThreadLocal 到底是什么了, 又知道了如何使用 ThreadLocal 以及其基本实现原理了是不是就可以结束了呢? 其实还有一个问题就是 ThreadLocalMap 是个什么对象, 为什么要用这个对象呢?
ThreadLocalMap 对象是什么
本质上来讲, 它就是一个 Map, 但是这个 ThreadLocalMap 与我们平时见到的 Map 有点不一样
1、它没有实现 Map 接口;
2、它没有 public 的方法, 最多有一个 default 的构造方法, 因为这个 ThreadLocalMap 的方法仅仅在 ThreadLocal 类中调用, 属于静态内部类
3、ThreadLocalMap 的 Entry 实现继承了 WeakReference<ThreadLocal<?>>
4、该方法仅仅用了一个 Entry 数组来存储 Key, Value; Entry 并不是链表形式, 而是每个 bucket 里面仅仅放一个 Entry;
至于为什么这么实现的代码分析, 明天继续!!!

正文完
 0