ThreadLocal使用总结
使用场景:几个子线程要访问具有同一个初始值的变量,在各自的线程中创建该变量的副本,并使用各自的副本。
使用方法:创建一个全局的ThreadLocal
变量globalFeild
,一般使用private static
修饰,在各个子线程通过globalFeild.get()
方法获取属于各自线程的副本,每个线程使用和修改自己的副本,相互之间不干扰。典型的使用场景如下:比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。
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;
}
}
getConn() 会被几个线程访问,每个线程都会创建自己的Connection,并且保存在ThreadLocal变量中
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
- 既无共享,何来同步问题,又何来解决同步问题一说?
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
ThreadLocal的实现原理
每个线程都有自己的一个HashMap:ThreadLocalMap
,key是ThreadLocal变量本身,value就是变量内部包含的值。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
读取值的方法:
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();
}
public T get() --- 返回当前线程可用的实际value值
获取当前线程,拿到当前线程的filed:ThreadLocalMap, map的key是threadLocal变量,
value是实际的值
如果map为空
调用ThreadLocal的初始化函数初始化value值
new一个ThreadLocalMap,并存入一个键值对,key是this,即当前ThreadLocal本身引用
value是刚才初始化的value值
注意map的实现方式,内部是一个Entry数组,数组的元素不是链表,
当key求得的下标冲突时候,使用线性探测发,将下标加一后再判断
且Entry继承了WeakReference<ThreadLocal<?>>
包含1个field:value,就是存入的value值.
key被用在初始化父类构造函数了,
super(key),在后面从map读取数据的时候拿来和传入的key比较
如果map不为空
根据key,拿到entry的值
返回entry的value值。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
initialValue()通常被重写。
set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
参考使用案例:
http://www.jasongj.com/java/threadlocal/
另一个例子:下面的类为每个线程生成不同的ID,当某个线程第一次调用Thread.get()时,会为该线程赋予一个ID,并且在后续的调用中不再改变。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
参考: