这两天对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的处理。
分享到:
相关推荐
8.3.1 使用模板和回调机制 8.3.2 Spring为不同持久化技术所提供的模板类 8.4 数据源 8.4.1 配置一个数据源 8.4.2 获取JNDI数据源 8.4.3 Spring的数据源实现类 8.5 小结 第9章 Spring的事务管理 9.1 数据库事务基础...
8.3.1 使用模板和回调机制 8.3.2 Spring为不同持久化技术所提供的模板类 8.4 数据源 8.4.1 配置一个数据源 8.4.2 获取JNDI数据源 8.4.3 Spring的数据源实现类 8.5 小结 第9章 Spring的事务管理 9.1 数据库事务基础...
│ 15 TheadLocal的基本概念.mp4 │ 16 ThreadLocal的工作原理.mp4 │ 17 ThreadLocal的注意事项.mp4 │ 18 【总结】线程基础.mp4 │ 19 【总结】线程同步.mp4 │ ├─02 第二章 原子操作(资料在本章) │ ...
│ 15 TheadLocal的基本概念.mp4 │ 16 ThreadLocal的工作原理.mp4 │ 17 ThreadLocal的注意事项.mp4 │ 18 【总结】线程基础.mp4 │ 19 【总结】线程同步.mp4 │ ├─02 第二章 原子操作(资料在本章) │ ...
│ 15 TheadLocal的基本概念.mp4 │ 16 ThreadLocal的工作原理.mp4 │ 17 ThreadLocal的注意事项.mp4 │ 18 【总结】线程基础.mp4 │ 19 【总结】线程同步.mp4 │ ├─02 第二章 原子操作(资料在本章) │ ...
│ 15 TheadLocal的基本概念.mp4 │ 16 ThreadLocal的工作原理.mp4 │ 17 ThreadLocal的注意事项.mp4 │ 18 【总结】线程基础.mp4 │ 19 【总结】线程同步.mp4 │ ├─02 第二章 原子操作(资料在本章) │ ...
Scala是描述配置的理想语言使用宏而不是反射来实现位置跟踪可大大提高性能控制台和日志文件的自己的性能记录器后端可组合的记录器,将不同的记录器与Monoid[Logger[F]]结合在一起SLF4J API的后端站在cats-effect...
ueditor-piggsoft对 java工具包改写##为什么要重写?简单易用,开源,功能丰富。...加入,封装Conf和HttpServletRequest,且成为TheadLocal变量。加入,将字符串变量统一管理。加入,不再从map中取值.
Java面试题+Java并发编程(J.U.C)+Java8实战+Redis+kafka Java 『必看』2021 版最新Java 学习路线图(持续刷新):+1::+1::+1: Java入门面试题 ...内存泄露的原因找到了,罪魁祸首居然是Java TheadLocal ..