本文主要探讨以下几个问题:
- ThreadLocal是为解决什么问题而存在
- 用锁机制实现的ThreadLocal有什么问题
- 源码阅读剖析
ThreadLocal 设计初衷
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
用锁机制实现的ThreadLocal有什么问题
先手写个用锁实现的ThreadLocal,再看有什么问题
public class MyThreadLocal<T> {
/**
* 存放副本变量的map容器,以Thread为key,变量副本为value
*/
private Map<Thread, T> threadMap = new HashMap<>();
public synchronized T get(){
return threadMap.get(Thread.currentThread());
}
public synchronized void set(T value){
threadMap.put(Thread.currentThread(), value);
}
}
这样写实现了ThreadLocal主要功能,以当前Thread为key、有存取两函数、加锁保证数据安全。问题也很明显,存取函数上有锁导致线程阻塞严重。
怎样既能保证不使用锁又能保证数据的安全呢?
源码阅读剖析
以ThreadLocal.get()开始
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
做了几件事
- 获取当前线程对象的属性 threadLocals
- 以当前ThreadLocal为key 取出ThreadLocalMap.Entry中的值
看下 ThreadLocalMap 的实现
static class ThreadLocalMap {
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocalMap是ThreadLocal的内部静态类,内部又有静态内部类Entry。
Entry比较简单,以ThreadLocal为key,以Object为value,是ThreadLocal值存储的真正位置。ThreadLocal内有Entry数组,每个Entry都是个键值对,数组有类似map的功能。
Thread内有ThreadLocalMap属性。
/* ThreadLocal values pertaining to this thread. This map is
maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Thread、ThreadLocalMap、Entry关系
class Thread{
ThreadLocalMap threadLocals;
}
class ThreadLocalMap{
Entry[] table;
}
class Entry{
ThreadLocal<?> k;
Object v;
}
实际的逻辑如下图
总结
我们的get方法其实拿到的是每个线程独有的ThreadLocalMap,然后再用ThreadLocal的当前实例,拿到Map中的相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map为空,还会先进行map的创建,初始化等工作。
与synchronized对比
ThreadLocal 和 synchronized 都用于解决多线程并发问题,这俩却有本质区别。synchronized是利用锁机制,使变量或代码块在某一时刻只能被一个线程访问。而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问的并非同一个对象,隔离多个线程对数据的共享。