首页 > 代码库 > ThreadLocal小记
ThreadLocal小记
API:
public class ThreadLocal<T>
- extends Object
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
Method:
initialValue
protected T initialValue()
- 返回此线程局部变量的当前线程的“初始值”。线程第一次使用
get()
方法访问变量时将调用此方法,但如果线程之前调用了set(T)
方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用get()
后又调用了remove()
,则可能再次调用此方法。该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。
- 返回:
- 返回此线程局部变量的初始值
get
public T get()
- 返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用
initialValue()
方法返回的值。 - 返回:
- 此线程局部变量的当前线程的值
set
public void set(T value)
- 将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠
initialValue()
方法来设置线程局部变量的值。 - 参数:
value
- 存储在此线程局部变量的当前线程副本中的值。
remove
public void remove()移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程 读取,且这期间当前线程没有 设置其值,则将调用其
initialValue()
方法重新初始化其值。这将导致在当前线程多次调用initialValue 方法。例1:
package com.example; import java.util.Date; import java.util.concurrent.TimeUnit; public class SafeTask implements Runnable{ private static ThreadLocal<Date> startDate= new ThreadLocal<Date>() { protected Date initialValue(){ return new Date(); } }; @Override public void run() { System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get()); try { TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get()); } public static void main(String[] args) { SafeTask task=new SafeTask(); for (int i=0; i<3; i++){ Thread thread=new Thread(task); thread.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
Starting Thread: 15 : Mon Nov 17 13:36:33 CST 2014 Starting Thread: 17 : Mon Nov 17 13:36:35 CST 2014 Thread Finished: 17 : Mon Nov 17 13:36:35 CST 2014 Starting Thread: 18 : Mon Nov 17 13:36:37 CST 2014 Thread Finished: 15 : Mon Nov 17 13:36:33 CST 2014 Thread Finished: 18 : Mon Nov 17 13:36:37 CST 2014
从结果,可以看到,三个线程各自存储和访问各自维护的startDate局部变量。
解析:
ThreadLocal有一个内部静态类ThreadLocalMap来管理线程中的变量,可以简单的将其理解成一个Map。
而Map中存放的指的方式是:用当前的线程来做为KEY,线程对应的变量值作为VALUE。
当某一线程要获取当前变量的值时,就使用ThreadLocal.get()方法,通过线程自身作为KEY,去ThreadLocalMap中查找对应的值。
这样就可以解释“每个线程都保持对其线程局部变量副本的隐式引用”。
而为了使每个线程都可以使用该变量的副本使用,“ThreadLocal 实例通常是类中的 private static 字段”。
为了更好的理解ThreadLocal这种机制,请看下面的例子。
例2:
package com.example; import java.util.Date; import java.util.concurrent.TimeUnit; public class SafeTask implements Runnable{ private static ThreadLocal<Date> startDate = new ThreadLocal<Date>(); @Override public void run() { startDate = new ThreadLocal<Date>(); startDate.set(new Date()); System.out.printf("Thread: %s new startDate.\n", Thread.currentThread().getId()); System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get()); try { TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get()); } public static void main(String[] args) { SafeTask task=new SafeTask(); for (int i=0; i<3; i++){ Thread thread=new Thread(task); thread.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
Thread: 15 new startDate. Starting Thread: 15 : Mon Nov 17 13:55:16 CST 2014 Thread: 17 new startDate. Starting Thread: 17 : Mon Nov 17 13:55:18 CST 2014 Thread Finished: 15 : null Thread Finished: 17 : Mon Nov 17 13:55:18 CST 2014 Thread: 18 new startDate. Starting Thread: 18 : Mon Nov 17 13:55:20 CST 2014 Thread Finished: 18 : Mon Nov 17 13:55:20 CST 2014
为什么例2的结果中会出现null呢?
原因就在于,代码在线程中重新创建来ThreadLocal
startDate = new ThreadLocal<Date>();这样做,就导致了startDate指向了新的ThreadLocal对象,那么之前存放在其中的副本就丢失了,所以才会出现null的情况。
通过以上的例子,我们就能理解为什么ThreadLocal 实例通常是类中的 private static 字段。
我们需要明白,每个线程中变量的副本,是通过线程的KEY和变量的VALUE存放在ThreadLocalMap中,而不是说,为每个线程建立ThreadLocal。
就类而言,他实际只维护和管理着一个ThreadLocal。
PS:代码部分截取来自《Java 7 Concurrency Cookbook》
ThreadLocal小记