原文链接: http://javapapers.com/core-java/threadlocal/
以下为译文:
线程变量
线程变量核心理念是:每条线程可以通过get和set方法访问它自己的,独立初始化的一份对象的拷贝。
目录
线程变量介绍
什么是线程安全
线程变量的使用场景
线程变量使用例子
线程变量在java API中的使用
线程变量与内存泄漏
线程变量介绍
每个线程需要一个独立的对象实例以至于在多线程并发的时候不会有并发冲突。每个实例对每个线程都是唯一的。这只不过是线程安全的一种实现。
线程变量有一个重要的特点就是它是全局的。它可在线程的任何地方被访问到。还要注意到它总是被声明为static和final.
什么是线程安全
线程是一个单一的线性的执行过程。当我们提到多线程的应用的时候是指多个线程的执
行的过程在执行相同的代码行。在这种情况下,有一定的几率一条线程会进入或者修改另
一条线程的数据。当我们的数据不能那样共享的情况下,我们需要将它作为线程安全的。
下面
是一些实现线程安全的不同方法。
重入锁
互斥
线程变量
原子操作
在上面的列表中,线程变量只是线程安全的一个技术选择。我们现在明白了线程变量的使用场景。
线程变量的使用场景
这里为情不自禁的来引用Joshua Bloch的原话:
每个线程真正的上下文,如用户id和事物id。非常有用,当线程退出时易于清理,没有
内存泄漏。
每个线程一个对象实例可以带来高性能。
在无法直接控制的回调函数中传递变量:有时候我们通过第三方的包来回调到我们自
己的包中代码。在这种场景下,由于第三方包的能力缺陷,你不能将你需要的参数传递给
你自己的回调函数中。对于这种罕见的场景,线程变量正好能发挥它的用武之地。
上面这些用我自己的话说就是:我们有一个对象不是线程安全的,但是我们希望安全的使用它。我们可以将这个对象放在synchronized块中以达到同步的目的。另一个选择就是使用线程安全,每个线程都持有一个独立的对象来达到线程安全的目的。
线程变量使用的例子
package com.javapapers; import java.text.SimpleDateFormat; import java.util.Date; public class ThreadLocalExample { private static final ThreadLocal formatter = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd HHmm"); } }; public String formatIt(Date date) { return formatter.get().format(date); } }
在上面的实例代码中,get()方法是需要理解的关键点。它返回此线程局部变量的当前线程副本中的值,如果在当前线程中没有这个值,它首先通过initialValue方法去初始化这个值,然后返回。
javadoc的例子
下面的类为每条线程产生一个唯一的本地标识。这个线程id会在第一次调用ThreadId.get()方法的时候被赋值,并且在随后的调用中保持不变。
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal threadId = new ThreadLocal() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
线程变量在java API中的使用
在JDK1.7中我们有一个新的类叫做ThreadLocalRandom。它可以用来为每一个并行执行的线程产生随机数,随机种子对每个线程来说都是唯一的。这是一个很酷的实现。
下面的代码就是线程变量在ThreadLocalRandom中的实现
private static final ThreadLocal localRandom = new ThreadLocal() { protected ThreadLocalRandom initialValue() { return new ThreadLocalRandom(); } };
使用ThreadLocalRandom的例子
package com.javapapers; import java.util.concurrent.ThreadLocalRandom; public class ThreadLocalRandomExample { public static void main(String args[]) throws InterruptedException { //tossing 3 coins for (int i = 0; i < 3; i++) { final Thread thread = new Thread() { public void run() { System.out.print(Thread.currentThread().getName() + ":"); // generating 3 random numbers - random for every thread for (int j = 0; j < 3; j++) { final int random = ThreadLocalRandom.current().nextInt(1, 3); System.out.print(random + ","); } System.out.println(); } }; thread.start(); thread.join(); } } }
线程变量与内存泄漏
线程变量不是魔鬼,它是一个非常有用的API。所有的一切都是建立在我们怎么使用它的基础上。我们需要学会在不同的场景下使用不同的工具。我们不能因为大炮打不了蚊子而 责怪大炮没有用。
网络上大量的关于线程变量导致内存泄漏的抱怨。这“基本上”是不可能的。
还是来引用Joshua Bloch的话:
“线程变量在本质上没有任何问题:它们不导致内存泄漏,它们是高效的,它们比那些非线程变量更加本地化(因为它们具有更好的隐蔽性)。它们可能被错误的使用,当然,这样的事情在其他编程工具上面也时常发生....”
为什么说基本上不会发生内存泄漏呢,在“self-referential structures”这种场景下还是会发生内存泄漏的,参考 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6254531