这两天对ThreadLocal了解了下,通过google,很多文章都多是同一个说法,ThreadLocal为每个使用该变量的线程提供独立的变量副本,刚开始的时候就是这样理解的:假如说线程A和线程B共享变量c,那么通过ThreadLocal呢,我们就可以通过使用ThreaLocal这个类来使线程A和线程B各自拥有贡献变量c的副本,这样就不用锁了,就可以线程安全了,嘿嘿 ,性能和安全双收。恩,觉得这思想so good,那我就来写个小例子吧。
代码如下:
先是共享变量c的代码:
public class Student {
int i;
String name;
public Student(int i, String name) {
super();
this.i = i;
this.name = name;
seqNum.set(this);
}
@Override
public String toString() {
return "name:" + name + " age:" + i;
}
接着test类
public class ThreadLocalTest {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//new 一个线程共享的student对象
Student s = new Student(8, "张三");
//线程one
new Thread(new ThreadT(s), "one").start();
//线程two
new Thread(new ThreadT(s), "two").start();
//打印共享对象的信息
p("smain: " + s);
//主线程睡眠一下,保证线程one和线程two已经运行完
Thread.sleep(1000);
//再次打印共享对象的信息
p("main: " + s);
}
private static void p(Object o) {
System.out.println(o);
}
private static class ThreadT implements Runnable {
private Student student;
private ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>();
public ThreadT(Student s) {
super();
this.student = s;
//将s放发threadLocal中
studentThreadLocal.set(s);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
//从ThreadLocal中取得所说的共享对象student副本(注意这是错误的想法的示例)
Student s = studentThreadLocal.get();
if(null == s) {
p(threadName + ": get()为null执行set()方法");
// p(student);
studentThreadLocal.set(this.student);
//再次从ThreadLocal中获取
s = studentThreadLocal.get();
}
//改变贡献对象副本的对象的属性(这也是错误想法的示例)
s.name= "李四";
// studentThreadLocal.set(s);
p(threadName + ": " + studentThreadLocal.get());
}
}
结果运行结果是
two: get()为null执行set()方法
two: name:李四 age:8
one: get()为null执行set()方法
smain: name:张三 age:8
one: name:李四 age:8
main: name:李四 age:8
可以看到,在线程中的改变对会反映到共享变量中,不是说会帮我们生产副本吗?
于是我就去大概看看了ThreadLocal的源码,发现ThreadLocal的get方法是从ThreadLocalMap中去取,存呢是将当前线程为key,value为我们调用方法set的参数,让后我着重看了map的set方法,他是直接将我们向set方法所传的对象的引用放到map里的,并没有什么副本啊。难道是我那里弄错了?
然后我去看看别个的例子,发现别个和我写的例子蛮大的区别的。于是乎我就把我的例子改了下。
student如下:
public class Student {
int i;
String name;
public Student(int i, String name) {
super();
this.i = i;
this.name = name;
studentThreadLocal.set(this);
}
@Override
public String toString() {
return "name:" + name + " age:" + i;
}
public static ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>() ;
}
和之前没有什么区别,就是多了个ThreadLocal静态变量
在看测试类
public class ThreadLocalTest {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//new 一个线程共享的student对象
Student s = new Student(8, "张三");
//线程one
new Thread(new ThreadT(s), "one").start();
//线程two
new Thread(new ThreadT(s), "two").start();
//打印共享对象的信息
p("smain: " + s);
//主线程睡眠一下,保证线程one和线程two已经运行完
Thread.sleep(1000);
//再次打印共享对象的信息
p("main: " + s);
}
private static void p(Object o) {
System.out.println(o);
}
private static class ThreadT implements Runnable {
private Student student;
// private ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>();
public ThreadT(Student s) {
super();
this.student = s;
//将s放发threadLocal中
// studentThreadLocal.set(s);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
//从ThreadLocal中取得所说的共享对象student副本(注意这是错误的想法的示例)
// Student s = studentThreadLocal.get();
/* if(null == s) {
p(threadName + ": get()为null执行set()方法");
studentThreadLocal.set(this.student);
//再次从ThreadLocal中获取
s = studentThreadLocal.get();
}*/
//改变贡献对象副本的对象的属性
// s.name= "李四";
p(threadName + ": " + getStudent());
/* System.out.println("thread["+threadName +
"] sn["+s.getNextNum()+"]");*/
}
public Student getStudent(){
Student s = Student.studentThreadLocal.get();
if(null == s) {
if(Thread.currentThread().getName().equals("one")) {
Student.studentThreadLocal.set(new Student(9,"李四"));
}else {
Student.studentThreadLocal.set(new Student(10,"王五"));
}
}
return Student.studentThreadLocal.get();
}
}
这次run方法就是调用了getStudent方法。直接去取,取不到就new个放进去,
这次的运行结果
two: name:王五 age:10
one: name:李四 age:9
smain: name:张三 age:8
main: name:张三 age:8
这次好像很正常,mian方法里的输出是没有什么变化。
可是仔细看看代码,发现在线程中压根都没有用到传递进去的student, 没取到是放进去的是new出来的一个
,好,我们不new,直接将传递进去的student放进去,在取出来,再改变值后再放进去,
对getStudent做如下改变:
public Student getStudent(){
Student s = Student.studentThreadLocal.get();
if(null == s) {
if(Thread.currentThread().getName().equals("one")) {
Student.studentThreadLocal.set(new Student(9,"李四"));
}else {
// Student.studentThreadLocal.set(new Student(10,"王五"));
//将共享变量放入
Student.studentThreadLocal.set(this.student);
//再次将它取出去
s = Student.studentThreadLocal.get();
//改变值
s.i = 10;
s.name = "王五";
}
}
return Student.studentThreadLocal.get();
}
这次输出结果:
one: name:李四 age:9
smain: name:张三 age:8
two: name:王五 age:10
main: name:王五 age:10
这回又main方法的边了。
副本呢,副本在那!
这下就郁闷了,边看源码和网上的文章,从源码来看,
先看ThreadLocal的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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get方法会先得到当前的线程,然后再从当前的线程中获得ThreadLocalMap(这个ThreadLocalMap有点奇怪哦,它是在ThreadLocal中定义的内部类,却是Thread类的一个成员变量),
然后在map中以调用get方法的ThreadLocal的threadLocalHashCode(不明白怎么意思)和map里面的table.lenth来获取下面是获取的代码:
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
set方法和get方法类似。就不说了
在从例子方面说,
每次都是先去取,取不到的时候就new一个让后在放进去。这个和共享变量好像根本没什么关系,
这东西感觉和web的session很像,一个个线程就像是一个个会话,一个会话共享一个session,而一个线程通过一个ThreadLocal的set方法放个对象的引用进去,那么在这个线程的任何时刻都可以同过这个ThreadLocal去得到set进去的对象的引用。
结论:ThreadLocal并没有解决共享变量的问题,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,是在自己线程中产生的,其他线程是不需要访问的,也访问不到的。只不过,通过ThreadLocal的set的对象,在这个线程的任意时刻都是可以通过set的那个ThreadLocal的get获得set进去的对象的引用,和web的session有点类似。特别的,要是set进去的对象是共享变量,那还是会有并发的错误。
使用的情况:对每一个线程都必须持有一个类的实例,而且这个类是可变的(不可变的就是线程安全的,全部线程使用一个就可以了),例如hibernate对session的处理。