本节书摘来异步社区《Java 7并发编程实战手册》一书中的第1章,第1.10节,作者:【西】Javier Fernández González,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.10 线程局部变量的使用
共享数据是并发程序最核心的问题之一,对于继承了Thread类或者实现了Runnable接口的对象来说尤其重要。
如果创建的对象是实现了Runnable接口的类的实例,用它作为传入参数创建多个线程对象并启动这些线程,那么所有的线程将共享相同的属性。也就是说,如果你在一个线程中改变了一个属性,所有线程都会被这个改变影响。
在某种情况下,这个对象的属性不需要被所有线程共享。Java并发API提供了一个干净的机制,即线程局部变量(Thread-Local Variable),其具有很好的性能。
本节中,我们将创建两个程序:第一个具有刚才提到的问题,另一个使用线程局部变量机制解决了这个问题。
准备工作
本节的范例是在Eclipse IDE里完成的。无论你使用Eclipse还是其他的IDE(比如NetBeans),都可以打开这个IDE并且创建一个新的Java工程。
范例实现
按照接下来的步骤实现本节的范例
1.使要实现的范例具有之前提到的共享问题。创建一个名为UnsafeTask的类,它实现了Runnable接口。声明一个私有的java.util.Date属性。
public class UnsafeTask implements Runnable{
private Date startDate;```
2.实现run()方法,这个方法将初始化startDate属性,并且将值打印到控制台,让线程休眠一个随机时间,然后再次将startDate的值打印到控制台。
@Override
public void run() {
startDate=new Date();
System.out.printf("Starting Thread: %s : %sn",Thread. currentThread().getId(),startDate);
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);
}`
3.实现这个有问题的应用程序的主程序。创建一个包含main()方法的Main类。这个方法将创建一个UnsafeTask类对象,用它作为传入参数创建10个线程对象并启动这10个线程,每个线程的启动间隔2秒。
public class Core {
public static void main(String[] args) {
UnsafeTask task=new UnsafeTask();
for (int i=0; i<10; i++){
Thread thread=new Thread(task);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}```
4.在下面的截屏中,你将看到这个程序执行的结果。每个线程有一个不同的开始时间,但是当它们结束时,三个线程都有相同的startDate属性值。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/99c4b18a096f0ba73d8b725a51b90efe41ab1aee.png" width="" height="">
</div>
5.如之前提到的,我们将使用线程局部变量机制来解决这个问题。
6.创建一个SafeTask类,用以实现Runnable接口
public class SafeTask implements Runnable {
7.声明一个ThreadLocal对象。这个对象是在initialValue()方法中隐式实现的。这个方法将返回当前日期。
private static ThreadLocal startDate= new
ThreadLocal<Date>() {
protected Date initialValue(){
return new Date();
}
};```
8.实现run()方法。它跟UnsafeTask类的run()方法实现了一样的功能,但是访问startDate属性的方式改变了。
@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());
}```
9.这个范例的入口类与第一个范例一样,只是创建并作为参数参入的Runnable类对象不同而已。
10.运行范例,并分析两个范例之间的不同。
工作原理
在下面的截屏中,你将看到安全线程类的执行结果。现在,这3个线程对象都有它们自己的startDate属性值。
线程局部变量分别为每个线程存储了各自的属性值,并提供给每个线程使用。你可以使用get()方法读取这个值,并用set()方法设置这个值。如果线程是第一次访问线程局部变量,线程局部变量可能还没有为它存储值,这个时候initialValue()方法就会被调用,并且返回当前的时间值。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/c0e418d3668796c2de1d761bbc5873ebbf84c340.png" width="" height="">
</div>
更多信息