java中的threadlocal深入理解-亚博电竞手机版

提到threadlocal,有些android或者java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对threadlocal的理解和认识,希望让大家理解threadlocal更加透彻一些。

threadlocal是什么

threadlocal是一个关于创建线程局部变量的类。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用threadlocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

global && local

上面的两个修饰看似矛盾,实则不然。

  • global 意思是在当前线程中,任何一个点都可以访问到threadlocal的值。
  • local 意思是该线程的threadlocal只能被该线程访问,一般情况下其他线程访问不到。

用法简介

创建,支持泛型

threadlocal mstringthreadlocal = new threadlocal<>();

set方法

mstringthreadlocal.set("droidyue.com");

get方法

mstringthreadlocal.get();

完整的使用示例

private void testthreadlocal() {     thread t = new thread() {         threadlocal mstringthreadlocal = new threadlocal<>();          @override         public void run() {             super.run();             mstringthreadlocal.set("droidyue.com");             mstringthreadlocal.get();         }     };      t.start(); }

threadlocal初始值

为threadlocal设置默认的get初始值,需要重写initialvalue方法,下面是一段代码,我们将默认值修改成了线程的名字

threadlocal mthreadlocal = new threadlocal() {     @override     protected string initialvalue() {       return thread.currentthread().getname();     } };

android中的应用

在android中,looper类就是利用了threadlocal的特性,保证每个线程只存在一个looper对象。

static final threadlocal sthreadlocal = 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

展开全文
内容来源于互联网和用户投稿,文章中一旦含有亚博电竞手机版的联系方式务必识别真假,本站仅做信息展示不承担任何相关责任,如有侵权或涉及法律问题请联系亚博电竞手机版删除

最新文章

网站地图