一 问题的引出
我们来看这样一段代码:
public class Demo1 {
private static int number = 0;
public static void main(String[] args) {
Thread[] threads = new Thread[8];
for(int i=0;i<8;i++){
threads[i] = new Thread(()->{
number += 10;
System.out.println(Thread.currentThread().getName()+": number="+number);
});
}
for(int i=0;i<8;i++){
threads[i].start();
}
}
}
执行结果:
Thread-0: number=20
Thread-5: number=50
Thread-3: number=40
Thread-1: number=30
Thread-2: number=20
Thread-6: number=70
Thread-4: number=60
Thread-7: number=80
我们定义了一个静态int变量number,初始值为0;
main方法中,我们定义了一个线程数组,每个线程都获取number的值并且+10后打印。
从结果中,我们可以发现:
① 线程的执行是无序的;
② 线程的拿到的值也是相互干扰后的,是被其他线程修改过的;
③ 不同的线程也可能得到相同的值。
如果我们希望number的值相互不干扰的话,怎么做呢?
我们如何将共享资源在不同线程间进行隔离呢?
二 ThreadLocal
我们使用ThreadLocal来解决上述问题,请看如下代码:
public class Demo2 {
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[8];
for (int i=0;i<8;i++){
threads[i] = new Thread(()->{
int num = local.get();
//设置local值
local.set(num += 10);
System.out.println(Thread.currentThread().getName() + ": number="+num);
});
}
for (int i=0;i<8;i++){
threads[i].start();
}
}
}
执行结果为:
Thread-1: number=10
Thread-0: number=10
Thread-4: number=10
Thread-2: number=10
Thread-7: number=10
Thread-6: number=10
Thread-5: number=10
Thread-3: number=10
可以看到,每个线程获得的值都是0,设置后都为10。
这就是ThreadLocal起到的作用。
protected Integer initialValue() { return 0; }
这里返回的值,是每个线程独有的。
三 ThreadLocal的线程隔离机制
ThreadLocal中的每一个线程,都会存在一个ThreadLocalMap的对象,这个对象是个集合,这个集合里面存储的是key-value形式的数据,key代表的是我们定义的ThreadLocal 对象,value代表的是我要存储在当前线程里的数据。
我们通过代码来印证上述内容:
先来看ThreadLocal源码(部分)
public class ThreadLocal<T> {
...
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//通过getMap方法,从当前线程中获取ThreadLocalMap对象
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();
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//返回传入线程的threoadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
......
ThreadLocal的set方法:local.set(num += 10);
中,
首先获取当前线程,然后调用getMap方法。
getMap
方法返回了传入线程的threadLocals
。
我们从Thread类可以知道threadLocals的定义:ThreadLocal.ThreadLocalMap threadLocals = null;
它是个ThreadLocalMap类型的变量。
继续看set
方法,
如果Thread.threadLocals不为空,直接从将ThreadLocal对象和数据值(num)传入;
如果Thread.threadLocals为空,则为当前线程新创建一个ThreadMap,将ThreadLocal对象和数据值作为参数传入。当前线程
、当前线程的threadLocals(ThreadLocalMap)
、以及local(ThreadLocal<Integer>)
三者的关系如下图所示:
这幅图也充分表示了不同线程都有一份自己的ThreadMap,里面存放了ThreadLocal local。