使用线程局部变量

Java 9并发编程指南 目录

殊的重要性。

如果创建一个实现Runnable接口的类对象,然后使用相同的Runnable对象执行不同的线程对象,那么所有的线程将共享相同的属性。这意味着如果改变某个线程的一个属性,那么所有线程都会因此而受影响。

某些时候,我们需要一个不会被所有线程共享的属性。Java并发API提供一个具备高性能的清洁机制,称为线程局部变量。不过线程局部变量也有缺点,由于它们在线程执行期间保留值,这在线程重复使用的情况下会出现问题。

本节中,开发两个程序:一个暴露之前描述中出现的问题,另一个使用线程局部变量机制解决这个问题。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 首先,实现第一个程序。创建名为UnsafetTask的类,并指定其实现Runnable接口。定义一个私有的java.util.Date属性:

    public class UnsafeTask implements Runnable {
    	private Date startDate;
    
  2. 实现UnsafeTask对象的run()方法,此方法将初始化startDate属性,在控制台中输出属性值,随机休眠一段时间,然后再次输出startDate属性值:

    	@Override
    	public void run() {
    		startDate = new Date();
    		System.out.printf("Starting Thread: %s : %s\n", 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个线程,每个线程之间休眠2秒钟:

    public class Main {
    	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. 在下图中,可以看到程序的执行结果。每个线程有一个不同的启动时间,但是当线程结束时,属性值发生了变化,所以输出了错误数据。例如查看编号13的线程:

    pics/01_06.jpg

  5. 如之前提到的,我们将使用线程局部变量机制解决这个问题。

  6. 创建名为SafeTask的类,指定其实现Runnable接口:

    public class SafeTask implements Runnable{
    
  7. 定义ThreadLocal类的对象,此对象具有一个包含initialValue()方法的隐性实现。此方法将返回实际日期:

    	private static ThreadLocal<Date> startDate = new ThreadLocal<Date>(){
    		protected Date initialValue(){
    			return new Date();
    		}
    	};
    
  8. 实现run()方法,此方法与UnsafeTask类的run()方法功能相同,但是它改变了存取startDate属性的方式,我们将使用startDate对象的get()方法:

    	@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. 运行程序,分析区别。

工作原理

在下图中,可以看到安全程序的执行结果。10个线程对象具有各自的startDate属性值:

pics/01_07.jpg

线程局部变量机制存储每个使用这些变量的线程的一个属性值,使用get()方法读取这个值,以及set()方法改变这个值。当第一次存取线程局部变量时,如果它调用的线程对象没有值,线程局部变量将调用initialValue()方法给线程赋值并且返回这个初始值。

扩展学习

线程局部类提供了remove()方法,用来删除线程调用的线程局部变量存储的值。

Java并发API包含InheritableThreadLocal类,继承从一个线程创建的线程变量值。如果线程A有一个线程局部变量值,并且A创建了线程B,B将会具有和A中的线程局部变量相同的值。在线程局部变量中,通过覆盖此类的childValue()方法初始化子线程的值,并将父线程的值以参数形式接收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值