使用线程局部变量

并发应用的一个最重要的方面是共享数据。

如果您创建一个实现 Runnable 接口的类对象并使用同一个 Runnable 对象启动不同的 Thread 对象,所有的线程共享同样的属性。 这意味着如果您在一个线程中改变一个属性,所有线程都将受此改变的影响。

有时您想有一个属性不在所有线程中共享。Java Concurrency API提供了一个名为thread-local变量的机制,简洁,性能也不错。

任务

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt1.sect10 package中

  • 线程类 : 不安全线程 (UnsafeTask)

    • java.util.Date 类型的私有属性 startDate
        private Date startDate;
    
    • 在 run 方法中设置 startDate 的值后随机睡几秒再输出 startDate 的值
        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);
    
  • 线程类 : 安全线程 (SafeTask)

    • 声明一个 ThreadLocal<Date> 类对象并实现 initialValue 方法,在此方法中返回实际时间
        private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
            protected Date initialValue() {
                return new Date();
            }
        };
    
    • run 方法和 UnsafeTask 基本一致,只是不再需要设置 startDate 的值,获取其值的方法也变为 startDate.get()
        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());
    
  • 控制类 (Main)

    • 创建 UnsafeTask 类型变量并用此变量逐个创建并启动 10 个线程,每个线程间隔 2 秒
        UnsafeTask unsafeTask = new UnsafeTask();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(unsafeTask);
            thread.start();
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    • 创建 SafeTask 类型变量并按前述方法创建并启动10个线程

工作原理

  • Thread-local变量为每个用到这个变量的线程保存一个属性值。
    • 使用 get() 方法取值
    • 使用 set() 方法设置
  • 第一次访问thread-local变量的值时,如果对于访问的线程来说它还没有值,则它调用 initialValue() 方法为该线程赋一个值

了解更多

  • thread-local类也提供了 remove() 方法,为调用此方法的线程删掉保存在此thread-local变量中的值
  • Java Concurrency API 包含 InheritableThreadLocal 类,为从某个线程创建的线程提供继承的值。
    • 如果线程A在一个thread-local变量中有一个值,它创建了另一个线程B,线程B将有和线程A在thread-local变量中同样的值。
    • 可覆盖 childValue() 方法可调用来初始化子线程的thread-local变量中的值。它通过一个参数来获知父线程的thread-local变量中的值。