Android中的多线程并发场景,通常是通过线程同步的方式去保证线程安全。对于共享资源,需要通过syncronized或者Lock等线程同步方法实现资源的互斥访问,才能确保数据访问的正确性。
今天要介绍的ThreadLocal为解决多线程并发问题提供了一种新思路。想必熟悉Java的同学对ThreadLocal并不陌生,今天我们就一起来探讨ThreadLocal。
本文将从以下几方面对ThreadLocal进行讲解:
- ThreadLocal介绍
- 源码角度分析ThreadLocal的原理
- ThreadLocal的应用
ThreadLocal介绍
首先,ThreadLocal是什么?它的名字看起来有点奇怪,其实,它是用来存放线程局部变量的容器,也许把它命名为ThreadLocalVar更为合适。ThreadLocal的功能其实非常简单,就是为每一个使用该变量的线程提供一份变量的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,ThreadLocal就像是线程的本地变量一样,线程之间相互独立。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个线程绑定了私有的本地实例存取空间,从而为多线程中的并发访问问题提供了一种隔离机制。
从时间和空间的角度来说,对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者提供变量,让不同的线程互斥访问,而后者为每一个线程都提供了一份变量,可以同时访问而互不影响。也就是说,如果你定义了一个属性,而你并不需要在不同的线程中共享这个属性,只需要它在各个线程中独立的变化且互不影响,ThreadLocal会是一个不错的选择。
源码角度分析ThreadLocal原理
为了搞清楚ThreadLocal的原理,我们来看一下ThreadLocal的源码,通过源码分析一下它是怎么实现线程本地变量的功能。
ThreadLocal有一个关键内部类ThreadLocalMap,它是线程真正用来保存本地变量的容器。每一个线程都会有单独的ThreadLocalMap实例,它保存了这个线程所有的本地变量。接下来我们就分析一下ThreadLocal的两个最主要的get()和set()方法。
ThreadLocal的set()方法用来设置当前线程中变量的副本,get()方法用来获取ThreadLocal在当前线程中保存的变量副本。
public T get() {
//获得当前线程
Thread t = Thread.currentThread();
//获得当前线程的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
//如果不为空,说明此线程已经有一个ThreadLocalMap实例
if (map != null) {
//map中保存线程所有的本地变量,获得当前本地变量的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//e不为空说明当前本地变量存在,返回对应的value值
if (e != null)
return (T)e.value;
}
//如果map不存在或map中不存在当前本地变量,返回初始值
return setInitialValue();
}
//返回线程对象的threadLocals成员变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
//获取初始化值,initialValue是需要覆写的初始化方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果map不为空,将初始化值放入到当前线程的ThreadLocalMap对象中
if (map != null)
map.set(this, value);
else
//当前线程第一次使用本地线程变量,需要对map进行初始化工作
createMap(t, value);
//返回初始化值
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
//获得当前线程的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
if (map != null)
//将变量的值保存在ThreadLocalMap中,key值为当前线程本地变量
map.set(this, value);
else
createMap(t, value);
}
代码中对重要的代码逻辑都做了注释。我们看到ThreadLocal的关键就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程分别有一个ThreadLocalMap副本,它只能被当前线程所读取和修改。ThreadLocal类通过操作每一个线程独特的ThreadLocalMap副本,实现了变量访问在不同线程中的隔离。ThreadLocalMap存储的键对中的键值是指向ThreadLocal对象(this),而值就是你所设置的对象了。
ThreadLocal的应用
下面我们通过一个简单的例子来看一下ThreadLocal的使用方式和效果。
public class ThreadLocalTest {
//覆盖ThreadLocal的initialValue()方法设置初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
//获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
ThreadLocalTest sn = new ThreadLocalTest();
//创建3个线程,共享sn
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private ThreadLocalTest sn;
public TestClient(ThreadLocalTest sn) {
this.sn = sn;
}
public void run() {
//每个线程打出3个序列值
for (int i = 0; i < 3; i++) {
System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]");
}
}
}
}
上面的例子中,创建了三个TestClient线程,共享同一个ThreadLocalTest实例,并产生一组数字序列。运行代码输出如下:
thread[Thread-0] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[3]
thread[Thread-2] --> sn[3]
从输出结果来看,虽然每个线程所产生的序号共享同一个ThreadLocalTest实例,但它们之间并不会相互干扰,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每个线程提供了独立的副本。虽然是 static 变量,对于不同的线程并没有共享,而是每个线程各一份,从而也就保证了线程安全。
总结
ThreadLocal主要用于解决多线程并发的问题。它为每个线程提供了一个数据的副本,线程通过访问各自的副本,大大减少了线程同步带来的性能损耗,也降低了多线程并发控制的成本。
Synchonized和ThreadLocal虽然都是用于解决多线程并发访问,但是它们的使用场景有所不同。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。具体来讲,synchronized利用锁机制使共享资源在某一时该只能被一个线程访问,而ThreadLocal为每个线程都提供了变量的副本,隔离了多线程对数据的数据共享。
欢迎关注我的公众号一起交流学习
参考文章:
http://my.oschina.net/huangyong/blog/159489
http://www.cnblogs.com/dolphin0520/p/3920407.html