`

ThreadLocal-分析-总结

阅读更多

ThreadLocal<T>类在Spring,Hibernate等框架中起到了很大的作用,对于其工作原理,很多网上的文章分析的不够彻底,甚至有些误解。

 

首先,为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类(内部类)

1.ThreadLocalMap

2.Thread

可能有人会觉得Thread与ThreadLocal有什么关系,其实真正的奥秘就在Thread类中的一行:

 

ThreadLocal.ThreadLocalMap threadLocals = null;

 

 其中ThreadLocalMap的定义是在ThreadLocal类中,真正的引用却是在Thread类中

 

那么ThreadLocalMap究竟是什么呢?

 

可以看到这个类应该是一个Map,JDK的解释是

 

 写道
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values
 

 

接下来的重点是ThreadLocalMap中用于存储数据的entry

 

static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

 

 从中我们可以发现这个Map的key是ThreadLocal变量,value为用户的值,并不是网上大多数的列子key是线程的名字或者标识

 

到这里,我们就可以理解ThreadLocal究竟是如何工作的了

 

1.Thread类中有一个成员变量叫做ThreadLocalMap,它是一个Map,他的Key是ThreadLocal类

2.每个线程拥有自己的申明为ThreadLocal类型的变量,所以这个类的名字叫'ThreadLocal':线程自己的(变量)

3.此变量生命周期是由该线程决定的,开始于第一次初始(get或者set方法)

4.由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非共享或者拷贝

 

/**
 * @author mxdba
 *
 */
public class ThreadLocalSample {

	public static void main(String[] args) {
		ThreadTest test1 = new ThreadTest(10);
		ThreadTest test2 = new ThreadTest(20);
		test1.start();
		test2.start();
	}

}

/**
 * 此线程有两个ThreadLocal变量,但是由于ThreadLocal是延迟初始的,
 * 所以在debug时可以看到线程名为“线程20”的线程的ThreadLocalMap中没有thLcal2这个entry
 * @author mxdba
 * 
 */
class ThreadTest extends Thread {
	
	public static ThreadLocal<Integer> thLocal = new ThreadLocal<Integer>();
	public static ThreadLocal<String> thLocal2 = new ThreadLocal<String>();
	
	public Integer num;
	
	
	
	public ThreadTest(Integer num) {
		super("线程" + num);
		this.num = num;
	}

	@Override
	public void run() {
		Integer n = thLocal.get();
		if(num != 20) {
			String s = thLocal2.get();
		}
			
		if(n == null) {
			thLocal.set(num);
		}
		System.out.println(thLocal.get());
	}
	
}

 

 

接下来分析一下源码,就更加清楚了

 

/**
 * 关键方法,返回当前Thread的ThreadLocalMap
 * [[[每个Thread返回各自的ThreadLocalMap,所以各个线程中的ThreadLocal均为独立的]]]
 */
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 

 

 

 

public T get() {
        Thread t = Thread.currentThread();
        /**
         * 得到当前线程的ThreadLocalMap
         */
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            /**
             * 在此线程的ThreadLocalMap中查找key为当前ThreadLocal对象的entry
             */
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
 

 

 

 

private T setInitialValue() {
        /**
         * 默认返回null,这个方法为protected可以继承
         */
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            /**
             * 初次创建
             */
            createMap(t, value);
        return value;
    }
 

 

 

/**
 * 给当前thread初始ThreadlocalMap
 */
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 

 

 

通过上边的分析,我们发现,ThreadLocal类的使用虽然是用来解决多线程的问题的,但是还是有很明显的针对性

1.最明显的,ThreadLoacl变量的活动范围为某线程,并且我的理解是该线程“专有的,独自霸占”,对该变量的所有操作均有该线程完成!也就是说,ThreadLocal不是用来解决共享,竞争问题的。典型的应用莫过于Spring,Hibernate等框架中对于多线程的处理了

 

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;  
}  

 这段代码,每个线程有自己的ThreadLocalMap,每个ThreadLocalMap中根据需要初始加载threadSession,这样的好处就是介于singleton与prototype之间,应用singleton无法解决线程,应用prototype开销又太大,有了ThreadLocal之后就好了,对于需要线程“霸占”的变量用ThreadLocal,而该类实例的方法均可以共享。

 

2.关于内存泄漏:

虽然ThreadLocalMap已经使用了weakReference,但是还是建议能够显示的使用remove方法。

  • 大小: 9.9 KB
  • 大小: 13.9 KB
分享到:
评论
25 楼 在世界的中心呼喚愛 2015-03-18  
少数讲的清楚的文章
24 楼 fejay 2010-10-29  
说的很清晰,之前不明白是干什么的.支持下楼主
23 楼 zdjray 2010-10-29  
说的确实非常对,不是其他文章中说的以线程对象为key而是以ThreadLocal对象为key,这样才能够使得同一线程有多个ThreadLocal存在.
但是有一处我觉得可能很多人都忽略了并且极其重要:
其实ThreadLocal的关键在于initialValue()这个方法,以及初始set的做法.
因为保存引用变量value时还是用的原来的引用,假如不从initialValue()斩断的话,还是不能满足要求的.
22 楼 Jeff.h 2010-10-22  
感谢楼主分享!特意去读了下源代码,说下自己的理解:
    每个线程都拥有一个自己的ThreadLocalMap对象,而这个对象的key是ThreadLocal类型的,这个key在每个线程中都是一样的,ThreadLocal只是作为一个索引。

    之所以有线程局部变量这一说,是因为每个线程的ThreadLocalMap对象不同,那ThreadLocal类型的key当然可以指向不同对象了。
21 楼 xuyan2680 2010-10-11  
mxdba321123 写道
xuyan2680 写道
存在线程池时,再次启用这个线程时,threadlocal原来的值还在吗?ps: 可能跟线程池的实现有关.


我不是很理解你的意思
ThreadTest test1 = new ThreadTest(10);
		ThreadTest test2 = new ThreadTest(20);
		
		ExecutorService executor = Executors.newFixedThreadPool(10);
		
		executor.execute(test1);
		//这个是你说的再次启用吗?
		executor.execute(test1);
		
		executor.shutdown();


再次启用相当于从新开启一个线程,多个线程间是没有关系的,所以不存在“原来”的值

将代码稍微改下,如果还存在则会打印出“num exisit”
if(n == null) {
			thLocal.set(num);
		} else {
			System.out.println("num exisit");
		}


tomcat 服务器线程池 就存在这样的情况,它是重启的空闲线程。 所以说与线程池的实现有点关系
20 楼 jcs7575 2010-10-11  
这个帖子很详细
多学习下源码 就会很清楚了
19 楼 sdtm1016 2010-10-10  
错了,想了想,

ThreadLocalMap中key是每个ThreadLocal

ThreadLocal是在get方法时被每个线程自己的ThreadLocalMap加入的

ThreadLocal根据每个线程来决定get()方法返回的值



18 楼 sdtm1016 2010-10-10  
mxdba321123 写道
明天的昨天 写道
有问题请教楼主:
ThreadLocalSample这个demo不是 启动两个线程来访问共享资源public Integer num;  吗?

论坛里面也有说:
"ThreadLocal 不是用来解决共享对象的多线程访问问题的"

这怎么理解啊?

demo中的num本身就不是ThreadLocal的,他就是普通的类变量,自然是线程共享的

num的存在只是为了在这句时起作用
引用

if(num != 20) { 
            String s = thLocal2.get(); 
        }

在DEBUG时可以看到如果num等于20的话,该线程的threadLocals:ThreadLocalMap中不会有thLocal2的记录,所以说明threadlocal是线程独立的。

也即-"ThreadLocal 不是用来解决共享对象的多线程访问问题的"



有一点我不太明白,在ThreadTest类中定义的2个ThreadLocal(thLocal,thLocal2),在初始化时会通过该线程的ThreadLocalMap把自身加入到该程中去,那么每个线程(test1,test2)的ThreadLocalMap应该都有这二个ThreadLocal吧,而且这二个ThreadLocal被定义为静态的,那么二个线程中引用的都是同样的ThreadLocal只不过key是每个线程吧?不知道这样理解对不对?望指教
17 楼 mxdba321123 2010-10-10  
明天的昨天 写道
有问题请教楼主:
ThreadLocalSample这个demo不是 启动两个线程来访问共享资源public Integer num;  吗?

论坛里面也有说:
"ThreadLocal 不是用来解决共享对象的多线程访问问题的"

这怎么理解啊?

demo中的num本身就不是ThreadLocal的,他就是普通的类变量,自然是线程共享的

num的存在只是为了在这句时起作用
引用

if(num != 20) { 
            String s = thLocal2.get(); 
        }

在DEBUG时可以看到如果num等于20的话,该线程的threadLocals:ThreadLocalMap中不会有thLocal2的记录,所以说明threadlocal是线程独立的。

也即-"ThreadLocal 不是用来解决共享对象的多线程访问问题的"
16 楼 明天的昨天 2010-10-10  
有问题请教楼主:
ThreadLocalSample这个demo不是 启动两个线程来访问共享资源public Integer num;  吗?

论坛里面也有说:
"ThreadLocal 不是用来解决共享对象的多线程访问问题的"

这怎么理解啊?


15 楼 udukwilliam 2010-10-09  
mark mark mark
14 楼 mxdba321123 2010-10-08  
xuyan2680 写道
存在线程池时,再次启用这个线程时,threadlocal原来的值还在吗?ps: 可能跟线程池的实现有关.


我不是很理解你的意思
ThreadTest test1 = new ThreadTest(10);
		ThreadTest test2 = new ThreadTest(20);
		
		ExecutorService executor = Executors.newFixedThreadPool(10);
		
		executor.execute(test1);
		//这个是你说的再次启用吗?
		executor.execute(test1);
		
		executor.shutdown();


再次启用相当于从新开启一个线程,多个线程间是没有关系的,所以不存在“原来”的值

将代码稍微改下,如果还存在则会打印出“num exisit”
if(n == null) {
			thLocal.set(num);
		} else {
			System.out.println("num exisit");
		}
13 楼 xuyan2680 2010-10-08  
存在线程池时,再次启用这个线程时,threadlocal原来的值还在吗?ps: 可能跟线程池的实现有关.
12 楼 mxdba321123 2010-10-08  
jiangzhouyun 写道
抄别人的吧

只有这段代码是“抄”的:
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; 


写这篇文章是由于之前淘宝电话面试时面试官提了一个问题,大概意思:
“说说SpringMVC,Hibernate这些框架是怎么解决多线程的”

我在阅读Spring相关书籍的时候看到过ThreadLocal在他们中的重要性,但是那时还真的说不清楚,也就干脆没说。

回来觉得非常惭愧,所以仔细研究了一下,当然这个过程中参考很多网上的资料,如果你说部分有些相似,那我承认,但是抄那确实没有。
11 楼 jiangzhouyun 2010-10-08  
抄别人的吧
10 楼 mxdba321123 2010-10-08  
msi110 写道
貌似ThreadLocal在JDK1.5以后才出现的么?

ThreadLocal 在JDK1.2时就已经出现了 呵呵
9 楼 msi110 2010-10-08  
貌似ThreadLocal在JDK1.5以后才出现的么?
8 楼 41897179 2010-10-08  
很好,刚看了下struts里的相关应用,再看这个很受用啊
7 楼 windzhq 2010-10-08  
说得简单易懂,让人一下就理清Thread和Threadlocal的关系
6 楼 kswwhyk 2010-10-08  
看了楼主的帖子让我对ThreadLoacl又有了更深的了解!

相关推荐

    并发编程总结.xmind

    java并发编程总结,包括多线程安全机制分析总结,Unsafe源码分析总结,并发工具类总结,ThreadLocal原理和使用,Fork/Join框架使用总结,同步容器和并发容器源码分析

    互联网创意产品众筹平台

    │ 7-登录功能异步开发总结 │ 8-MD5概述5 _* g* f: Y1 v* o4 H │ 9-MD5-工具类1 H6 x* t" K- z* B │ 10-登录功能-MD5密码加密' F4 `+ B( c' b5 I' ?7 a │ 11-注销功能9 z3 d8 y4 A0 l: p* n* @ │ 12-附录3.页面...

    汪文君高并发编程实战视频资源全集

     高并发编程第三阶段11讲 AtomicXXXFieldUpdater源码分析及使用场景分析.mp4  高并发编程第三阶段12讲 sun.misc.Unsafe介绍以及几种Counter方案性能对比.mp4  高并发编程第三阶段13讲 一个JNI程序的编写,通过...

    汪文君高并发编程实战视频资源下载.txt

     高并发编程第三阶段11讲 AtomicXXXFieldUpdater源码分析及使用场景分析.mp4  高并发编程第三阶段12讲 sun.misc.Unsafe介绍以及几种Counter方案性能对比.mp4  高并发编程第三阶段13讲 一个JNI程序的编写,通过...

    java面试题以及技巧

    │ │ │ Java程序员认证模拟题及详细分析.doc │ │ │ question.rar │ │ │ test4.doc │ │ │ 模拟题.rar │ │ │ 经典的104-147模拟题.rar │ │ │ │ │ ├─035 │ │ │ 2003.10.5.15.51.43.TestKing%...

    java面试题目与技巧1

    │ │ │ Java程序员认证模拟题及详细分析.doc │ │ │ question.rar │ │ │ test4.doc │ │ │ 模拟题.rar │ │ │ 经典的104-147模拟题.rar │ │ │ │ │ ├─035 │ │ │ 2003.10.5.15.51.43.TestKing%...

    java面试题及技巧4

    │ │ │ Java程序员认证模拟题及详细分析.doc │ │ │ question.rar │ │ │ test4.doc │ │ │ 模拟题.rar │ │ │ 经典的104-147模拟题.rar │ │ │ │ │ ├─035 │ │ │ 2003.10.5.15.51.43.TestKing%...

    java面试题及技巧3

    │ │ │ Java程序员认证模拟题及详细分析.doc │ │ │ question.rar │ │ │ test4.doc │ │ │ 模拟题.rar │ │ │ 经典的104-147模拟题.rar │ │ │ │ │ ├─035 │ │ │ 2003.10.5.15.51.43.TestKing%...

    java面试题以及技巧6

    │ │ │ Java程序员认证模拟题及详细分析.doc │ │ │ question.rar │ │ │ test4.doc │ │ │ 模拟题.rar │ │ │ 经典的104-147模拟题.rar │ │ │ │ │ ├─035 │ │ │ 2003.10.5.15.51.43.TestKing%...

    百度地图毕业设计源码-interview-guide:面试指南

    ThreadLocal 线程池 MySql redis 系统设计 TCP/IP 分布式事务 消息队列 源码 数据库分库分表 自己总结 Java JVM 内存模型 垃圾回收 如何设计一个可达性分析系统 类加载机制 双亲委派机制 SPI 并发 synchronized 偏向...

    java单例模式看这一篇就够了

    深入分析java单例模式什么是单例模式单例模式的常见写法一、饿汉式单例优点缺点示例二、懒汉式单例示例1(普通写法)示例2(synchronized写法)示例3(DCL写法)示例4(内部类写法)三、注册式单例示例1(容器式)示例2(枚举式...

Global site tag (gtag.js) - Google Analytics