Java Concurrency - ThreadLocal, 本地线程变量

共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量。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);
    };
};

 

posted on 2016-10-17 10:29  huey2672  阅读(231)  评论(0编辑  收藏  举报