前面Handler消息处理机制中提到了线程会将自己的Looper对象放到ThreadLocal中,因而我们有必要看看ThreadLocal是什么?
ThreadLocal是什么?
ThreadLocal也是用来进行多线程并发的,可以理解为是线程的局部变量,作用就是为每个线程提供一个变量值的副本,每个线程可以独立的改变自己的副本而不影响其他线程。
ThreadLocal与synchronized的区别?
两者都可以实现多线程的并发
ThreadLocal:采用空间换时间的方式
synchronized:采用时间换空间的方式
ThreadLocal是怎么做到为每个线程维护变量副本的呢?
每个线程内部都有一个ThreadLocalMap的变量,用来存储每个变量的副本,而ThreadLocalMap采用Entry数组来存储ThreadLocal---Object键值对;
在分析源码前,先来看看我们平常使用多线程的例子
实例1:
public class ThreadLocalTest {
public static void main(String[] args) {
Test test = new Test(0);
MyThread thread1 = new MyThread(test);
MyThread thread2 = new MyThread(test);
thread1.start();
thread2.start();
}
}
class MyThread extends Thread
{
public Test test;
public MyThread() {
}
public MyThread(Test test)
{
this.test = test;
}
@Override
public void run() {
for(int i = 0;i < 3;i++)
{
System.out.println(Thread.currentThread().getName()+" value: "+test.getNum());
}
}
}
class Test
{
public int count;
public Test(int count)
{
this.count = count;
}
public int getNum()
{
return count++;
}
}
输出:
Thread-0 value: 0
Thread-0 value: 2
Thread-0 value: 3
Thread-1 value: 1
Thread-1 value: 4
Thread-1 value: 5
可以发现线程0和线程1是交错的在改变count值的,因为两个线程共用Test里面的count变量,而且两者改变的顺序是不固定的;
实例2:(使用ThreadLocal后的情况)
public class ThreadLocalTest {
public static void main(String[] args) {
Test test = new Test();
MyThread thread1 = new MyThread(test);
MyThread thread2 = new MyThread(test);
thread1.start();
thread2.start();
}
}
class MyThread extends Thread
{
public Test test;
public MyThread() {
}
public MyThread(Test test)
{
this.test = test;
}
@Override
public void run() {
for(int i = 0;i < 3;i++)
{
System.out.println(Thread.currentThread().getName()+" value: "+test.getNum());
}
}
}
class Test
{
public ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
{
protected Integer initialValue()
{
return 0;
};
};
public int getNum()
{
threadLocal.set(threadLocal.get()+1);
return threadLocal.get();
}
}
输出:
Thread-1 value: 1
Thread-0 value: 1
Thread-1 value: 2
Thread-0 value: 2
Thread-1 value: 3
Thread-0 value: 3
发现线程0和1的输出并不是互相影响的,虽然他们共用test,但是对于count变量,他们各自都有自己的备份,修改的时候并不会影响另一个线程;
如果实例2还不明显的话,我们再来一个实例看看:
实例3:
public class ThreadLocalTest {
public static void main(String[] args) {
final Test test = new Test();
test.init();//设置ThreadLocal里面的值
System.out.println("ThreadID: "+test.intThreadLocal.get());
System.out.println("ThreadName: "+test.stringThreadLocal.get());
new Thread(){
public void run()
{
test.init();//设置ThreadLocal里面的值
System.out.println("ThreadID: "+test.intThreadLocal.get());
System.out.println("ThreadName: "+test.stringThreadLocal.get());
};
}.start();
//子线程执行结束之后休眠5秒钟,为了查看子线程中的ThreadLocal值是否与主线程相关
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadID: "+test.intThreadLocal.get());
System.out.println("ThreadName: "+test.stringThreadLocal.get());
}
}
class Test
{
ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
public void init()
{
intThreadLocal.set((int)Thread.currentThread().getId());
stringThreadLocal.set(Thread.currentThread().getName());
}
}
输出:
ThreadID: 1
ThreadName: main
ThreadID: 9
ThreadName: Thread-0
ThreadID: 1
ThreadName: main
可以发现main中我们首先输出了主线程的ThreadID以及主线程的名字,随后开启子线程输出子线程的ThreadID和子线程的名字,随后休眠程序5秒钟直接输出主线程的ThreadID以及主线程的名字,在这次输出时我们并没有通过test.init( )初始化ThreadLocal的值,但是他仍然会输出主线程的信息,这说明一点,只要设置了线程的ThreadLocal值之后,及时有别的线程开启也不会影响原来现成的ThreadLocal,也就是说ThreadLocal是线程独享的;
下面从源码角度进行分析:
ThreadLocal类提供了4种主要方法:
public T get() {}
public void set(T value) {}
public void remove() {}
protected T initialValue() {}
剩下的方法都是让这四种方法间接调用的,在JDK1.2之前,方法里面的T全部是Object
下面分别讲解每个方法的实现:
get( ):
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
解释:
get方法首先会获取当前线程,然后通过getMap方法获取到当前线程的ThreadLocalMap属性值,来看看getMap(Thread thread)方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
解释:getMap很简单,就是直接返回当前线程的属性值,也就是Thread里面肯定有threadLocals属性,果不其然
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
那么ThreadLocalMap又是什么东西呢?
他是ThreadLocal的静态内部类,源码如下:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
}
这个静态内部类里面又有一个静态内部类Entry,实现了WeakReference类,并且在类中将ThreadLocal通过super(k)调用WeakReference的构造函数将当前ThreadLocal设置成软引用,便于GC及时回收ThreadLocal占用的内存,从这里也就看出来ThreadLocalMap其实就是一个以ThreadLocal---Object为键值的Map;回到get方法,在获取到ThreadLocalMap之后,接下来分两种情况讨论,(1)如果map不为空的话,通过ThreadLocalMap的getEntry方法获取到当前ThreadLocal的键值对,这里getEntry传入的参数是ThreadLocal变量而不是当前线程,接着如果Entry值不为空的话,返回Entry的value值即可;(2)如果map为空的话,调用setInitialValue来生成一个具有默认值的ThreadLocal,并且返回这个值;
看下setInitialValue的源码:
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方法来获得设置的初始值,这个方法是protected修饰的,因此实际中是由我们自己来重写的,方法原型是:
protected T initialValue() {
return null;
}
随后获取当前线程的ThreadLocalMap属性值,如果这个值不为null的话,则将默认value值设置到当前的ThreadLocal中,如果这个值为空的话,则调用createMap创建一个ThreadLocalMap,他的初始里面仅仅包含一个键值对,键为当前的ThreadLocal,值为
initialValue设置的初始值,最后返回初始值即可;
好了,get方法分析完毕,其实他就是返回ThreadLocal在当前线程中保存的变量副本而已;
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);
}
看到没有呢?其实set方法的实现和i
nitialValue方法几乎是一样的,只不过一个是我们在使用的过程中设置的,一个是我们在初始化的时候设置的;
remove( )方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
也很简单,调用ThreadLocalMap的remove方法,从当前线程的ThreadLocalMap中移除当前ThreadLocal;总结一下:
(1)一个Thread中可以有多个ThreadLocal,他们是作为键存储在ThreadLocalMap中的,值为变量的值;
(2)我们在使用ThreadLocal的get方法之前要不先调用set方法,设置ThreadLocal的值,要不重写ThreadLocal的protected修饰的initialValue方法来初始化ThreadLocal值;否则会发生空指针异常;
实例4:
public class ThreadLocalTest {
public static void main(String[] args) {
final Test test = new Test();
test.init();//设置ThreadLocal里面的值
System.out.println("ThreadID: "+test.intThreadLocal.get());
System.out.println("ThreadName: "+test.stringThreadLocal.get());
}
}
class Test
{
ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
public int getInt()
{
return intThreadLocal.get();
}
public void init()
{
System.out.println(getInt());
intThreadLocal.set((int)Thread.currentThread().getId());
stringThreadLocal.set(Thread.currentThread().getName());
}
}
发现会在return intThreadLocal.get()这行报空指针异常;
解决:
public class ThreadLocalTest {
public static void main(String[] args) {
final Test test = new Test();
test.init();//设置ThreadLocal里面的值
System.out.println("ThreadID: "+test.intThreadLocal.get());
System.out.println("ThreadName: "+test.stringThreadLocal.get());
}
}
class Test
{
ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 0;
};
};
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
public int getInt()
{
return intThreadLocal.get();
}
public void init()
{
System.out.println(getInt());
intThreadLocal.set((int)Thread.currentThread().getId());
stringThreadLocal.set(Thread.currentThread().getName());
}
}
这样子就不会报空指针异常啦,原因很简单,我们在在get方法的源码分子中已经看到,如果当前ThreadLocalMap为空的话,会调用setInitialValue方法进行初始化,进而会调用到initialValue方法,这也就是protected方法,只要我们重写就可以设置我们想要初始化的值啦;
好了,ThreadLocal源码分析完毕,有什么错误还希望各位指正;