一、前言
我们在前面学习知道对象和变量的并发访问我们可以使用synchronized、volatile关键字。synchronized保证了数据的原子性,解决并发访问同一数据的时候排队执行。volatile保证了数据的可见性,使得在多线程共享一个数据的时候,一旦数据发生改变。各个线程都能拿到最新的数据的之。
而ThreadLocal的出现使得每个线程都有自己的一份副本,也就是使得每个线程都单独拥有自己的数据。达到线程之间的数据隔离。
二、ThreadLocal的使用
package com.it.test.thread;
public class ThreadLocalDemo {
static ThreadLocal<Integer> local1 = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
static class Thread1 extends Thread {
@Override
public void run() {
local1.set(1);
System.out.println(Thread.currentThread().getName()+":"+local1.get());
}
}
static class Thread2 extends Thread {
@Override
public void run() {
local1.set(2);
System.out.println(Thread.currentThread().getName()+":"+local1.get());
}
}
}
Thread-0:1
Thread-1:2
三、ThreadLocal源码分析
1、set方法
获取当前线程,拿到当前线程的ThreadLocalMap ,如果map为空,则创建一个map,否则之间给当前map设置值,key为当前ThreacLocal对象,值就是我们存放的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
来看下createMap方法,为当前线程设置了一个ThreadLocalMap对象,
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
来看下ThreadLocalMap的源码,包含了一个静态内部类Entry,继承了弱引用的ThreadLocal。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在上面为线程创建一个ThreadLocalMap对象的时候,调用了ThreadLocalMap的构造方法,在ThreadLocalMap的构造方法,构造方法传入了:
ThreadLocal<?> firstKey, Object firstValue两个参数。
接着内部创建了内部类的Entry类型数组,
table = new Entry[INITIAL_CAPACITY];
然后创建Entry对象,传入firstKey和firstValue,存入到Entry数组中。
这里需要注意的是,之所以创建了一个Entry数组,是因为一个线程可能有多个ThreadLocal对象,
每一个ThreadLocal,都会在ThreadLocalMap内部的Entry数组存入。
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设置值的时候,那么就会在当前线程中创建一个ThreadLocalMap,如果存在ThreadLocalMap则直接获取当前线程的ThreadLocalMap。而ThreadLocalMap包含一个Entry数组,Entry有两个属性key和value。使用ThreadLocal设置值的时候,就会把ThreadLocal对象作为key,以及具体设置的值,初始化给Entry对象,然后存入Entry类型的数组中。当有多个
ThreadLocal的时候,数组就会存入多个Entry对象。
2、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 。调用ThreadLocalMap 的getEntry方法。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
我们知道ThreadLocalMap 中的包含了一个Entry数组,getEntry方法,传入了ThreadLocal对象作为key,然后经过系列的位运算,算出数组的下标。最好从数组中拿到Entry对象,
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
拿到Entry对象,直接获取Entry对象里的value值,就是我们存入的value值。
小结
- ThreadLocal使得每个线程拥有自己的一份副本,也就是拥有自己的一份数据。达到线程直接数据隔离
- ThreadLocal的实现原理的核心就是:
(1)当我们在某个线程下第一次使用ThreadLocal设置值的时候,那么就会为这个线程创建一个ThreadLocalMap 对象,ThreadLocalMap 中包含了一个Entry数组。Entry中包含了两个属性key和value。
在设置值的时候,就会将我们的值存放到ThreadLocalMap 的Entry数组中,其中key为当前ThreadLocal对象,值就是我们设置的值。
(2)当我们使用ThreadLocal获取值的时候,首先是会获取当前线程的ThreadLocalMap ,然后获取ThreadLocalMap 中的数组。接着通过当前ThreadLocal作为key,通过位运算获取到属性key为当前ThreadLocal对象的Entry对象,在获取Entry中的value属性。就是我们需要获取的值。
网友评论