看了各路大神总结关于ThreadLocal的知识, 我也做了相关笔记, 然后在这里记录, 方便后面生疏之后再次学习.
1.基本概念
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行 的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
ThreadLocal类操作时候实际使用当前线程(Thread)中的一个ThreadLocalMap独立副本,每个线程可以独立修改属于自己的副本而不会互相应影响,避免了线程访问实例变量发生冲突的问题。
Thread中的ThreadLocalMap, 所有操作都是围绕这个map进行 :
2.查看源码
首先谈下ThreadLocal给我印象, 整体感觉像一个包装类或者说工具类, 每个线程的数据其实还是在自己线程内部通过threadLocals引用(ThreadLocalMap)得到自己数据, 只是通过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;
}
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
简单分析下流程 :
- 首先获取当前线程 .
- 根据当前线程获取对应的ThreadLocalMap .
- 如果获取Map不为空, 在Map中以ThreadLocal的引用为key在Map中获取value, 否则转到5 .
- 如果获取value不为null, 就返回, 否则转到5 .
- 如果Map为空, 或者value为null, 就调用initialValue函数初始化值value, 然后用ThreadLocal引用和value作为key和value创建一个新的Map, 并放在当前Thread中.
注意 :
每一个Thread维护一个ThreadLocalMap的映射表, 这个映射表的key是ThreadLocal实例本身, value就是真正要存的Object .
JDK早期设计使用Thread作为当前线程的key, 后来换了思路 . 使用ThreadLocal实例作为key, 这样的显著的好处就是: 每个Map的Entry数量变大了 : 之前是Thread数量(个人感觉只能存一份数据), 现在是ThreadLocal的实例数量, 我们可以定义多份ThreadLocal实例对象, 可以存放不同类型数据 .
3.应用场景
hibernate中经典应用 :
可以看到getSession方法中, 首先判断当前线程有没有放进去session, 如果还没有通过sessionFactory().openSession()创建一个session, 再将session set到线程中, 实际是放到ThreadLocalMap这个map中, 这时对于这个session的唯一引用就是当前线程中那个ThreadLocalMap, 而threadSession(ThreadLocal实例)作为这个值的key, 要取到这个session可以通过threadSession.get()获得, 里面执行的操作实际是先取到当前线程的ThreadLocalMap, 然后将threadSession作为key将值取出, 这个session相当于线程的私有变量. 而不是public. 显然其他线程是取不到这个session, 他们只能取到自己ThreadLocalMap中东西 .
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
4.简单测试
通过下面代码可以看到各个线程的value是相互独立的, 本线程累加操作不会影响其他线程值, 达到线程内部隔离效果 .
public class TestThreadLocal {
private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 0;
};
};
public static void main(String[] args) {
for(int i=0;i<5;i++) {
new Thread(new MyThread(i)).start();
}
}
static class MyThread implements Runnable{
private int index;
public MyThread(int index) {
this.index = index;
}
@Override
public void run() {
// 每个线程都设置初始值1
tl.set(1);
handler();
}
public void handler() {
System.out.println("线程"+index+"初始值:"+tl.get());
for(int i=0;i<10;i++) {
tl.set(tl.get()+1);
}
System.out.println("线程"+index+"结果值:"+tl.get());
}
}
}
打印结果值 :
线程4初始值:1
线程0初始值:1
线程1初始值:1
线程2初始值:1
线程3初始值:1
线程2结果值:11
线程1结果值:11
线程0结果值:11
线程4结果值:11
线程3结果值:11
个人感觉ThreadLocal最主要好处就是避免了方法频繁传递参数, 我们可以不使用ThreadLocal存放局部变量, 线程执行过程中所有方法都传递这个参数, 但是这样会使程序显得凌乱.