防止任务在共享资源上产生冲突的一种方式是根除对变量的共享,使用线程的本地存储为使用相同变量的不一样线程建立不一样的存储。java
下面是一个 ThreadLocal 的实例。这里咱们使用了静态的全局变量 ThreadLocal
对象来保存 Integer
类型的值。咱们在不一样的线程中将指定的数字传入到 threadLocal
中进行保存。而后,再将其读取出来:数据库
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String...args) {
threadLocal.set(-1);
ExecutorService executor = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
final int ii = i; // i不能是final的,建立临时变量
executor.submit(new Runnable() {
public void run() {
threadLocal.set(ii);
System.out.println(threadLocal.get());
}
});
}
executor.shutdown();
System.out.println(threadLocal.get());
}
复制代码
从程序的执行结果能够看出,每一个线程都正确地读取出来了保存到 ThreadLocal 中的数据。数组
因此,咱们总结一下 ThreadLocal
的做用就是,存储在 ThreadLocal
中的变量是线程安全的,每一个线程只能读取出本身存储的值。安全
一般它的使用方式就是定义一个静态全局的 ThreadLocal
实例,而后每一个线程使用它来读写只有本身会用到的数据。好比,咱们要为每一个线程建立了一个数据库链接,而且该链接只容许该线程本身使用,那么能够将它存储在 ThreadLocal
中,而后在用到的地方获取。数据结构
看了上面的例子,也许你会又如下一些问题:this
带着上面的这些问题,咱们来看下在JDK源码中 ThreadLocal
是如何实现的。spa
咱们仍是先从读取的操做来看。线程
如下是 ThreadLocal
中 set()
方法的代码:code
public T get() {
Thread t = Thread.currentThread(); // 1
ThreadLocalMap map = getMap(t); // 2
if (map != null) { // 3
ThreadLocalMap.Entry e = map.getEntry(this); // 4
if (e != null) {
T result = (T) e.value; // 5
return result;
}
}
return setInitialValue();
}
复制代码
这里首先会再步骤1中获取到当前线程的实例,而后在步骤2中经过getMap()
方法,使用当前的线程的ThreadLocalMap
。这里的ThreadLocalMap
的定义以下:cdn
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) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
// ...
}
复制代码
而后,咱们看下getMap()
方法的定义:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
也就是说实际上当咱们调用 get()
方法的时候,会先获取当前线程中的 threadLocals
字段,该字段是 ThreadLocalMap
类型的。而后,咱们使用当前的 ThreadLocal
实例做为键来从哈希表中获取到一个 Entry
,而实际的值就保存再 Entry
的 value
字段中。
就像上面的 getEntry()
方法定义的那样,彷佛这里的哈希表只是一个数组,那哈希冲突是怎么解决的呢?实际上,咱们知道一般解决哈希冲突有两种解决方式,一种是拉链法,一种是线性探测法。前者在 HashMap
和 ConcurrentHashMap
中使用较多,而这里用到的其实就是线性探测法。说白了就是将全部的值放在一个数组里面而后根据散列的结果到数组中取值,具体的实现方式能够看相关的数据结构知识点。
这里的关系是否是有点乱,咱们来捋一下:
咱们使用ThreadLocal
存储的值实际是存储在Thread
使用ThreadLocalMap
当中的,而这里的ThreadLocal
实例值起到了一个哈希表的键的做用:
就像上图显示的那样,假如咱们在线程thread1
中调用了threadLocal1
的get()
方法,首先会用Thread.currentThread()
方法获取到thread1
,而后获取到thread1
的threadLocals
实例,threadLocals
是一个ThreadLocalMap
类型的哈希表。而后,咱们再用threadLocal1
做为键来从threadLocals
中获取到值Entry
,并从Entry
中取出存储的值并返回。
至此,咱们已经了解了ThreadLocal的实现的原理,原本想看下set()
方法的,可是到此已经基本真相大白了,因此也就没有继续下去的必要了。
咱们回过头来看下以前提出的几个问题:
以上就是ThreadLocal的用法和实现原理。