共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量。Java API 提供了 ThreadLocal 来解决这个问题。

一个 ThreadLocal 作用的例子:

import java.util.Date;

public class Main {

    public static void main(String[] args) {
Runnable task = new Runnable() { private ThreadLocal<Date> dateVar = new ThreadLocal<Date>(); public void run() {
dateVar.set(new Date());
System.out.printf("%s, GET dateVar: %s\n", Thread.currentThread().getName(), dateVar.get());
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s, FINAL dateVar: %s\n", Thread.currentThread().getName(), dateVar.get());
}
}; for (int i = 0; i < 3; i++) {
String threadName = "Thread" + (i + 1);
Thread thread = new Thread(task, threadName);
thread.start();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}

观察运行结果:

Thread1, GET dateVar: Fri Oct 14 22:06:33 CST 2016
Thread2, GET dateVar: Fri Oct 14 22:06:34 CST 2016
Thread3, GET dateVar: Fri Oct 14 22:06:35 CST 2016
Thread1, FINAL dateVar: Fri Oct 14 22:06:33 CST 2016
Thread2, FINAL dateVar: Fri Oct 14 22:06:34 CST 2016
Thread3, FINAL dateVar: Fri Oct 14 22:06:35 CST 2016

可以看到每个线程都共用一个 Task 实例,线程之间间隔 1 秒启动。线程执行 run 方法的时候首先将 dateVar 的值设置为系统当前时间并打印 dateVar 值,然后线程会休眠 5 秒,最后再打印 dateVar 的值。注意到 run 方法设置 dateVar 值与最后打印 dateVar 值间隔 5 秒,而下一个线程启动时只间隔 1 秒,在当前线程打印 dateVar 之前,下个线程甚至是下下个线程已经重置 dateVar 的值,但是每个线程最后打印 dateVar 值的时候仍然是显示该线程最初设置的值。可见,每个线程中的 dateVar 并不会被其他线程所干扰。

再看下没有使用 ThreadLocal 的情况。

import java.util.Date;

public class Main2 {

    public static void main(String[] args) {
Runnable task = new Runnable() { private Date dateVar; public void run() {
dateVar = new Date();
System.out.printf("%s, GET dateVar: %s\n", Thread.currentThread().getName(), dateVar);
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s, FINAL dateVar: %s\n", Thread.currentThread().getName(), dateVar);
}
}; for (int i = 0; i < 3; i++) {
String threadName = "Thread" + (i + 1);
Thread thread = new Thread(task, threadName);
thread.start();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

观察运行结果:

Thread1, GET dateVar: Fri Oct 14 22:17:38 CST 2016
Thread2, GET dateVar: Fri Oct 14 22:17:39 CST 2016
Thread3, GET dateVar: Fri Oct 14 22:17:40 CST 2016
Thread1, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016
Thread2, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016
Thread3, FINAL dateVar: Fri Oct 14 22:17:40 CST 2016

可以看到,当直接使用 Date 类型时,线程最终打印 dateVar 的值与最初设置的值不一致。这个是因为所有的线程共享一个 Task 实例,所以 dateVar 是共享数据,在多个线程竞争时,造成数据不一致。

ThreadLocal 的初始化

可以通过覆盖 initialValue 方法初始化 ThreadLocal 的值。

ThreadLocal<Date> dateVar = new ThreadLocal<Date>() {
@Override
protected Date initialValue() {
return new Date();
}
};

InheritableThreadLocal

如果在线程 A 创建了子线程 B,那么线程 A 和 线程 B 都是各自维护一份 ThreadLocal 值,线程 A 的 ThreadLocal 值不会传递给子线程 B。Java API 提供了 InheritableThreadLocal 类,它是 ThreadLocal 的子类。如果使用 InheritableThreadLocal,那么在线程 A 创建子线程 B,线程 A 和 线程 B 仍然都是各自维护一份 InheritableThreadLocal 值,但是现场 A 的 InheritableThreadLocal 值则会传递给子线程 B。可以重写 childValue 方法修改从父现场继承的 InheritableThreadLocal 值。

public static ThreadLocal<Date> dateVar = new InheritableThreadLocal<Date>() {
protected Date initialValue() {
return new Date();
}; protected Date childValue(Date parentValue) {
return new Date(parentValue.getTime() + 1000L);
};
};

最新文章

  1. 完全卸载AndroidStudio
  2. java.io.EOFException
  3. Linux(二)__文件目录、常用命令
  4. Qt配置信息设置(QSettings在不同平台下的使用路径)
  5. c++关于接口机制和不完全类型的小问题
  6. poj 2711 Leapin&#39; Lizards &amp;&amp; BZOJ 1066: [SCOI2007]蜥蜴 最大流
  7. oc-02-NSLog使用
  8. 【转】 c++拷贝构造函数(深拷贝,浅拷贝)详解
  9. statspack系列4
  10. 企业管理系统开发笔记(4)---后台登录_MVC过滤器
  11. EBS查找运行请求时间,参数等
  12. CentOS7安装Postgresql
  13. Partition(hdu4651)2013 Multi-University Training Contest 5
  14. newborn, infant, toddler以及baby的区别
  15. 第一个spring,第一天。
  16. python技巧 is 和 ==
  17. PHP中namespace和use使用详解
  18. freePBX汉化方法记录——备忘
  19. 问题集录--新手入门深度学习,选择TensorFlow 好吗?
  20. poj 1475 推箱子

热门文章

  1. WS103C8例程——串口2【worldsing笔记】
  2. HDU 5832 A water problem (水题,大数)
  3. HDU 2045 不容易系列之(3)—— LELE的RPG难题 (递推)
  4. javabean总结
  5. setbuffer和freopen做一个简单的日志组件
  6. 更新证书错误Code Sign error: Provisioning profile ‘XXXX&#39;can&#39;t be found
  7. PostgreSQL的 initdb 源代码分析之三
  8. C++为什么不支持某些东西
  9. Codeforces Gym 100513D D. Data Center 前缀和 排序
  10. 利用ajax获取到的网页源码不能执行js代码