先上一个使用ThreadLocal实例的demo,ThreadLocalDemo 实例包含一个ThreadLocal实例。从网上各种信息看到ThreadLocal是线程私有变量。保持了每个变量的副本,其实ThreadLocal不能用于解决多线程共享变量问题。
ThreadLocal 中只是保存该线程自己创建的局部变量的副本。如果是多线程共享的变量还是会发生不能同步该的后果。下面这个例子就是启动两个线程,通过threadLocal实例的set方法将person实例加入到线程本地变量ThreadLocal.Values localValues;
中。但是localValues这个变量底层实现十基于数组的一个map结构。对于引用变量缓存引用。所以在这个demo中两个线程的localValues变量都指向了同一个person实例。也就不是线程私有的变量。要达到线程私有的话只有在线程中通过创建的变量通过ThreadLocal的set方法插入的元素才是线程私有的变量。
public class ThreadLocalDemo {
private static Person person;
private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
public ThreadLocalDemo( Person person ) {
this.person = person;
}
public static void main( String[] args ) throws InterruptedException {
// 多个线程使用同一个Person对象
Person per = new Person(111, "Sone");
ThreadLocalDemo test = new ThreadLocalDemo(per);
Thread th1 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(person);
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(888);
perLocal.setName("Admin");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th1");
Thread th2 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(person);
System.out.println(Thread.currentThread().getName() + " :"
+ threadLocal.get());
}
}, "thread-th2");
th1.start();
th1.join();
Thread.sleep(100);
th2.start();
th2.join();
// Person对象已经被线程给修改了!
System.out.println("Person对象的值为:" + per);
}
}
class Person {
private int id;
private String name;
public Person( int id , String name ) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
输出:
可以看到虽然将person实例加入到两个线程本地变量ThreadLocal.Values localValues;
但是都是指向同一个实例person。所以从输出结果可以看到在th1线程更改了person后,在th2线程也可以获取到最新的结果。
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=888, name=Admin]
thread-th2 :Person [id=888, name=Admin]
Person对象的值为:Person [id=888, name=Admin]
下面给出在线程里面new一个实例然后通过ThreadLocal类的set方法插入到当前线程的ThreadLocal.Values localValues;变量中
,最后对当前线程的localValues变量中的本地变量通过ThreadLocal类的get()方法获取到当前线程threadLocal实例为key对于的值。
public class ThreadLocalDemo {
private static Person person;
private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
//定义一个ThreadLocal类实例,这个是插入线程本地变量的接口类,
//有get/set方法;ThreadLocal类其实只是封装了插入线程本地变量的操作接口,
//每个线程的线程本地变量ThreadLocal.Values localValues就是一个map存储
//结构,以ThreadLocal类实例为key,存储的数据为值。
//如果需要获取到这个本地变量,只需要在线程内部通过ThreadLocal类实例的get()方法就可以获取到与ThreadLocal类实例对于的值。
public ThreadLocalDemo( Person person ) {
this.person = person;
}
public static void main( String[] args ) throws InterruptedException {
// 多个线程使用同一个Person对象
Person per = new Person(111, "Sone");
ThreadLocalDemo test = new ThreadLocalDemo(per);
Thread th1 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(new Person(111, "Sone"));
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(9999);
perLocal.setName("jim");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th1");
Thread th2 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
threadLocal.set(new Person(112, "vincent"));
String threadName = Thread.currentThread().getName();
Person perLocal = threadLocal.get();
System.out.println(threadName + " before:" + threadLocal.get());
perLocal.setId(8);
perLocal.setName("jack");
System.out.println(threadName + " after:" + threadLocal.get());
}
}, "thread-th2");
th1.start();
th2.start();
th1.join();
th2.join();
// Person对象已经被线程给修改了!
System.out.println("Person对象的值为:" + per);
}
public void run() {
}
}
class Person {
private int id;
private String name;
public Person( int id , String name ) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
输出结果:
从输出结果可以看到我在各自线程创建一个person实例,然后插入到线程本地变量中(ThreadLocal.Values localValues; 是一个map结构的数据结构),每个线程有具有一个这样的实例;插入使用的是相同的ThreadLocal类实例为key,但是缓存的是不同person变量。
thread-th2 before:Person [id=112, name=vincent]
thread-th2 after:Person [id=8, name=jack]
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=9999, name=jim]
Person对象的值为:Person [id=111, name=Sone]
最后说下看ThreadLocal类后对他的理解,首先ThreadLocal不是一个具体的线程。它是一个线程用于存取本地变量 ThreadLocal.Values localValues;的操作类,localValues是一个map类型的数据,key就是ThreadLocal,value就是插入的数据,在一个线程中可以插入不同ThreadLocal实例的数据,一个线程本地变量只能缓存特定ThreadLocal实例的一条数据。
在java中ThreadLocal以Map的形式存储数据(ThreadLocal对象为 key 数值为value)。在Android中做了些改动,在Thread-Local的add方法中,可以看到它会把ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。
先看下android的ThreadLocal类的源码,其中也就两个接口方法重要,get()和set(T data);
ThreadLocal数据插入流程
//set(T data)让线程插入一个key为当前ThreadLocal实例,value为value的键值对
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
//values(Thread current)这里就可以看到其实ThreadLocal类操纵的还是当前线程的本地变量。
Values values(Thread current) {
return current.localValues;
}
//然后对values判定是否为空,如果为空那么初始化一个空的Values实例,
//如下图就是初始化了一个空的Values类实例复制给了当前线程的
//ThreadLocal.Values localValues属性字段;
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
/**
* Creates a new, empty table with the given capacity.
*/
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
//最后调用Values实例的put方法完成了数据插入到map中,可以清晰看到key为ThreadLocal类实例,value就是set方法传进来的数据。
//下面是Values类的put方法处理逻辑,看到for循环时候,寻找插入位置时候先匹配到key,而key存放的位置比较特殊在数组下标的0 ,2, 4 ,6 ... 2n;这些位置,value存放位置在1,3,5,7...2n+1这些位置。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
//index是寻找key存放的下标, key.hash & mask寻找循环的起止位置,mask是table.length-1,默认是31,key.hash & mask计算后使得index一定指向key的下标。next()方法是对index加2操作。
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
ThreadLocal数据获取流程
//get获取数据
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);//获取到当前线程的本地变量map
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;//计算这个键值对存放的位置
if (this.reference == table[index]) {
return (T) table[index + 1];//返回结果
}
} else {
values = initializeValues(currentThread);//如果当前线程没有本地变量,初始化一个空的
}
return (T) values.getAfterMiss(this);//如果确实返回一个默认值。
}
首先得到一个Values对象,然后求出table数组ThreadLocal实例reference属性的下标。前文说过:ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。现在得到index后再index+1就是value在table数组中的下标。即value=table[index+1];return value即可。