java中的threadlocal深入理解-亚博电竞手机版
提到threadlocal,有些android或者java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对threadlocal的理解和认识,希望让大家理解threadlocal更加透彻一些。
threadlocal是什么
threadlocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用threadlocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
global && local
上面的两个修饰看似矛盾,实则不然。
- global 意思是在当前线程中,任何一个点都可以访问到threadlocal的值。
- local 意思是该线程的threadlocal只能被该线程访问,一般情况下其他线程访问不到。
用法简介
创建,支持泛型
threadlocalmstringthreadlocal = new threadlocal<>();
set方法
mstringthreadlocal.set("droidyue.com");
get方法
mstringthreadlocal.get();
完整的使用示例
private void testthreadlocal() { thread t = new thread() { threadlocalmstringthreadlocal = new threadlocal<>(); @override public void run() { super.run(); mstringthreadlocal.set("droidyue.com"); mstringthreadlocal.get(); } }; t.start(); }
threadlocal初始值
为threadlocal设置默认的get初始值,需要重写initialvalue方法,下面是一段代码,我们将默认值修改成了线程的名字
threadlocalmthreadlocal = new threadlocal () { @override protected string initialvalue() { return thread.currentthread().getname(); } };
android中的应用
在android中,looper类就是利用了threadlocal的特性,保证每个线程只存在一个looper对象。
static final threadlocalsthreadlocal = new threadlocal (); private static void prepare(boolean quitallowed) { if (sthreadlocal.get() != null) { throw new runtimeexception("only one looper may be created per thread"); } sthreadlocal.set(new looper(quitallowed)); }
如何实现
为了更好的掌握threadlocal,我认为了解其内部实现是很有必要的,这里我们以set方法从起始看一看threadlocal的实现原理。
下面是threadlocal的set方法,大致意思为
- 首先获取当前线程
- 利用当前线程作为句柄获取一个threadlocalmap的对象
- 如果上述threadlocalmap对象不为空,则设置值,否则创建这个threadlocalmap对象并设置值
源码如下
public void set(t value) { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) map.set(this, value); else createmap(t, value); }
下面是一个利用thread对象作为句柄获取threadlocalmap对象的代码
threadlocalmap getmap(thread t) { return t.threadlocals; }
上面的代码获取的实际上是thread对象的threadlocals变量,可参考下面代码
class thread implements runnable { /* threadlocal values pertaining to this thread. this map is maintained * by the threadlocal class. */ threadlocal.threadlocalmap threadlocals = null; }
而如果一开始设置,即threadlocalmap对象未创建,则新建threadlocalmap对象,并设置初始值。
void createmap(thread t, t firstvalue) { t.threadlocals = new threadlocalmap(this, firstvalue); }
总结:实际上threadlocal的值是放入了当前线程的一个threadlocalmap实例中,所以只能在本线程中访问,其他线程无法访问。
对象存放在哪里
在java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
问:那么是不是说threadlocal的实例以及其值存放在栈上呢?
其实不是,因为threadlocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而threadlocal的值其实也是被线程实例持有。
它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
关于堆和栈的比较,请参考java中的堆和栈的区别
真的只能被一个线程访问么
既然上面提到了threadlocal只对当前线程可见,是不是说threadlocal的值只能被一个线程访问呢?
使用inheritablethreadlocal可以实现多个线程访问threadlocal的值。
如下,我们在主线程中创建一个inheritablethreadlocal的实例,然后在子线程中得到这个inheritablethreadlocal实例设置的值。
private void testinheritablethreadlocal() { final threadlocal threadlocal = new inheritablethreadlocal(); threadlocal.set("droidyue.com"); thread t = new thread() { @override public void run() { super.run(); log.i(logtag, "testinheritablethreadlocal =" threadlocal.get()); } }; t.start(); }
上面的代码输出的日志信息为
i/mainactivity( 5046): testinheritablethreadlocal =droidyue.com
使用inheritablethreadlocal可以将某个线程的threadlocal值在其子线程创建时传递过去。因为在线程创建过程中,有相关的处理逻辑。
//thread.java private void init(threadgroup g, runnable target, string name, long stacksize, accesscontrolcontext acc) { //code goes here if (parent.inheritablethreadlocals != null) this.inheritablethreadlocals = threadlocal.createinheritedmap(parent.inheritablethreadlocals); /* stash the specified stack size in case the vm cares */ this.stacksize = stacksize; /* set thread id */ tid = nextthreadid(); }
上面代码就是在线程创建的时候,复制父线程的inheritablethreadlocals的数据。
会导致内存泄露么
有网上讨论说threadlocal会导致内存泄露,原因如下
- 首先threadlocal实例被线程的threadlocalmap实例持有,也可以看成被线程持有。
- 如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活
- 所以,threadlocal设定的值被持有,导致内存泄露。
上面的逻辑是清晰的,可是threadlocal并不会产生内存泄露,因为threadlocalmap做选择key的时候,并不是直接选择threadlocal实例,而是threadlocalmap实例的弱引用。
static class threadlocalmap { /** * the entries in this hash map extend weakreference, using * its main ref field as the key (which is always a * threadlocal object). note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. such entries are referred to * as "stale entries" in the code that follows. */ static class entry extends weakreference> { /** the value associated with this threadlocal. */ object value; entry(threadlocal k, object v) { super(k); value = v; } } }
所以实际上从threadlocal设计角度来说是不会导致内存泄露的。关于弱引用,了解更多,请访问译文:理解java中的弱引用
使用场景
- 实现单个线程单例以及单个线程上下文信息存储,比如交易id等
- 实现线程安全,非线程安全的对象使用threadlocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
- 承载一些线程相关的数据,避免在方法中来回传递参数
参考文章
- java threadlocal
- threadlocals and memory leaks in j2ee
- java thread local – how to use and code sample
- threadlocal in java – example program and tutorial