天天看点

Java ThreadLocal 使用详解

引言

“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副 本。threadlocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 id 或事务 id)相关联。”
Java ThreadLocal 使用详解

大概的意思有两点:

threadlocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离。

如果要使用threadlocal,通常定义为private static类型,在我看来最好是定义为private static final类型。

应用场景

threadlocal通常用来共享数据,当你想在多个方法中使用某个变量,这个变量是当前线程的状态,其它线程不依赖这个变量,你第一时间想到的

就是把变量定义在方法内部,然后再方法之间传递参数来使用,这个方法能解决问题,但是有个烦人的地方就是,每个方法都需要声明形参,多处声明,多处调用。

影响代码的美观和维护。有没有一种方法能将变量像private

static形式来访问呢?这样在类的任何一处地方就都能使用。这个时候threadlocal大显身手了。

实践

我们首先来看一段代码:

mport java.util.hashmap; 

import java.util.map; 

public class treadlocaltest { 

// static threadlocal<hashmap> threadlocal = new threadlocal<hashmap>(){ 

// @override 

// protected hashmap initialvalue() { 

// system.out.println(thread.currentthread().getname()+”initialvalue”); 

// return new hashmap(); 

// } 

// }; 

public static class t1 implements runnable { 

private final static map map = new hashmap(); 

int id; 

public t1(int id) { 

this.id = id; 

public void run() { 

// map map = threadlocal.get(); 

for (int i = 0; i < 20; i++) { 

map.put(i, i + id * 100); 

try { 

thread.sleep(100); 

} catch (exception ex) { 

system.out.println(thread.currentthread().getname() 

+ “# map.size()=” + map.size() + ” # ” + map); 

public static void main(string[] args) { 

thread[] runs = new thread[15]; 

t1 t = new t1(1); 

for (int i = 0; i < runs.length; i++) { 

runs[i] = new thread(t); 

runs[i].start(); 

这段程序的本意是,启动15个线程,线程向map中写入20个整型值,然后输出map。运行该程序,观察结果,我们会发现,map中压根就不止20个元素,这说明程序产生了线程安全问题。

我们都知道hashmap是非线程安全的,程序启动了15个线程,他们共享了同一个map,15个线程都往map写对象,这势必引起线程安全问题。

我们有两种方法解决这个问题:

将map的声明放到run方法中,这样map就成了方法内部变量,每个线程都有一份new hashmap(),无论多少个线程执行run方法,都不会有线程安全问题。这个方法也正如应用场景中提到的,如果有多处地方使用到map,传值是个烦人的地方。

将hashmap换成hashtable。用线程同步来解决问题,然而我们的程序只是想向一个map中写入20个整型的key-value而已,并不需要线程同步,同步势必影响性能,得不偿失。

threadlocal提供另外一种解决方案,即在解决方案a上边,将new

hashmap()得到的实例变量,绑定到当前线程中。之后从任何地方,都可以通过threadlocal获取到该变量。将程序中的注释代码恢复,再将

private final static map map = new hashmap();注释掉,运行程序,结果就是我们想要的。

实现原理

程序调用了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(); 

getmap方法的源码: 

threadlocalmap getmap(thread t) { 

return t.threadlocals; 

该方法返回的是当前线程中的threadlocalmap实例。阅读thread的源码我们发现thread中有如下变量声明:

/* threadlocal values pertaining to this thread. this map is maintained 

* by the threadlocal class. */ 

threadlocal.threadlocalmap threadlocals = null; 

我们暂时可以将threadlocalmap理解为一个类似map的这么个类,之后再讲解它。

get()方法的大致意思就是从当前线程中拿到threadlocalmap的实例threadlocals,如果threadlocals不为

空,那么就以当前threadlocal实例为key从threadlocals中拿到对应的value。如果不为空,那么就调用

setinitialvalue()方法初始化threadlocals,最终返回的是initialvalue()方法的返回值。下面是

setinitialvalue()方法的源码

private t setinitialvalue() { 

t value = initialvalue(); 

if (map != null) 

map.set(this, value); 

else 

createmap(t, value); 

return value; 

我们看到map.set(this, value);这句代码将threadlocalmap的实例作为key,将initialvalue()的返回值作为value,set到了threadlocals中。

程序在声明threadlocal实例的时候覆写了initialvalue(),返回了value,当然我们可以直接调用set(t t)方法来设置value。下面是set(t t)方法的源码:

public void set(t value) { 

我们看到它比setinitialvalue()方法就少了个return语句。这两种方式都能达到初始化threadlocalmap实例的效果。

我们再来看一下threadlocal类的结构。

threadlocal类只有三个属性,如下:

/*threadlocal的hash值,map用它来存储值*/ 

private final int threadlocalhashcode = nexthashcode();

/*改类能以原子的方式更新int值,这里主要是在产生新的threadlocal实例时用来产生一个新的hash值,map用该值来存储对象*/

private static atomicinteger nexthashcode = 

new atomicinteger(); 

/*该变量标识每次产生新的threadlocal实例时,hash值的增量*/

private static final int hash_increment = 0x61c88647;

剩下的就是一些方法。最关键的地方就是threadlocal定义了一个静态内部类threadlocalmap。我们在下一章节再来分析这个类。

从threadlocal的类结构,我们可以看到,实际上问题的关键先生是threadlocalmap,threadlocal只是提供了管理的功能,

我们也可以说threadlocal只是代理了threadlocalmap而已。

threadlocalmap源码分析

既然threadlocalmap实现了类似map的功能,那我们首先来看看它的set方法源码:

private void set(threadlocal key, object value) { 

// we don’t use a fast path as with get() because it is at 

// least as common to use set() to create new entries as 

// it is to replace existing ones, in which case, a fast 

// path would fail more often than not. 

entry[] tab = table; 

int len = tab.length; 

int i = key.threadlocalhashcode & (len-1); 

for (entry e = tab[i]; 

e != null; 

e = tab[i = nextindex(i, len)]) { 

threadlocal k = e.get(); 

if (k == key) { 

e.value = value; 

return; 

if (k == null) { 

replacestaleentry(key, value, i); 

tab[i] = new entry(key, value); 

int sz = ++size; 

if (!cleansomeslots(i, sz) && sz >= threshold) 

rehash(); 

这个方法的主要功能就是讲key-value存储到threadlocalmap中,这里至少我们看到key实际上是

key.threadlocalhashcode,threadlocalmap同样维护着entry数组,这个entry我们在下一节会讲解。这里涉及

到了hash冲突的处理,这里并不会向hashmap一样冲突了以链表的形式往后添加。如果对这个hash冲突解决方案有兴趣,可以再进一步研究源码。

既然threadlocalmap也是用entry来存储对象,那我们来看看entry类的声明,entry被定义在threadlocalmap的内部:

static class entry extends weakreference<threadlocal> { 

/** the value associated with this threadlocal. */ 

object value; 

entry(threadlocal k, object v) { 

super(k); 

value = v; 

这里我们看到entry集成了weakreference类,泛型声明了threadlocal,即每一个entry对象都保留了对

threadlocal实例的弱引用,之所以这么干的原因是,线程在结束之后需要将threadlocal实例从map中remove调,以便回收内存空

间。

总结

首先,threadlocalmap并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果,实际上自己在方法

中new出来变量也能达到类似的效果。threadlocalmap跟线程安全基本不搭边,绑定上去的实例也不是多线程公用的,而是每个线程new一份,

这个实例肯定不是共用的,如果共用了,那就会引发线程安全问题。threadlocalmap最大的用处就是用来把实例变量共享成全局变量,在程序的任何

方法中都可以访问到该实例变量而已。网上很多人说threadlocalmap是解决了线程安全问题,其实是望文生义,两者不是同类问题。

来源:51cto