ThreadLocal

现在,并发应用程序最关键的方面之一是共享数据。当你创建线程实现Runnable接口,然后开始各种线程对象使用相同的Runnable对象,所有线程共享,Runnable对象内部定义相同的属性。这本质上意味着,如果您更改了线程中的任何属性,所有线程都将受到此更改的影响,并将通过第一个线程看到修改后的值。有时它是你希望的行为,例如多个线程增加/减少相同的计数器变量;但有时您希望确保每个线程都必须工作在自己的线程实例副本上,并且不影响其他数据。

ThreadLocal

每个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储来一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值中终会对应的本地线程变量。

何时使用ThreadLocal

比如,你正在从事电子商务应用程序,需要为每个客户请求生成一个唯一的交易ID,控制器进程需要将此交易ID传递给Manager / DAO类中的业务方法,以便进行日志记录。一个解决方案可能是将此交易ID作为参数传递给所有业务方法。但这不是一个好的解决方案,因为代码是多余的和不必要的。

为了解决这个问题,在这里你可以使用ThreadLocal变量。你可以在控制器或任何预处理器拦截交易ID,并设置此交易ID到ThreadLocal里。在这之后,不管该控制器调用什么方法,它们都可以从ThreadLocal访问此交易ID。请注意,应用程序控制器将在一次服务多个请求,因为每个请求在单独的线程中在框架级别处理,交易ID将是每个线程唯一的,并且将从线程的执行路径中访问。

ThreadLocal API介绍

Java Concurrency API为ThreadLocal变量的使用提供良好的机制和优秀的性能。

public class ThreadLocal<T> extends Object {...}

该类提供线程本地变量。这些变量与一般的变量不同,每个线程访问一个线程(通过get或set方法)有自己独立的变量初始化副本。ThreadLocal实例通常是私有的静态字段在类希望关联状态的线程(例如,一个用户ID或交易ID)

这个类有以下方法:
get():返回该线程局部变量的当前线程的值复制。
initialvalue():返回该线程局部变量的当前线程的“初始值”。
remove():删除该线程局部变量的当前线程的值。
set(T value):将当前线程的本地线程变量的副本设置为指定的值。

如何使用ThreadLocal

下面的例子使用了两个线程局部变量,即threadId和startDate。它们都被定义为建议的“私有静态”字段。threadId将被用来确定当前正在运行的线程和startDate用来表示启动线程的执行的时间。以上信息将打印在控制台中,以验证每个线程是否保留了自己的变量副本。

public class DemoTask implements Runnable {
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    public static int getThreadId() {
        return threadId.get();
    }

    private static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
        @Override
        protected Date initialValue() {
            return new Date();
        }
    };


    @Override
    public void run() {
        System.out.printf("Starting Thread: %s : %s\n", getThreadId(), 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", getThreadId(), startDate.get());
    }

    public static void main(String[] args) {
        DemoTask demoTask = new DemoTask();

        Thread thread1 = new Thread(demoTask);
        Thread thread2 = new Thread(demoTask);
        Thread thread3 = new Thread(demoTask);

        thread1.start();
        thread2.start();
        thread3.start();

        try {
            thread1.join();
            thread2.join();
            thread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

现在来验证变量基本上能够保持自己的状态,不论多初始化为多少线程。我们创建了该任务的三个实例;启动线程;然后验证信息在控制台打印它们。

Starting Thread: 0 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 2 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 1 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 0 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 2 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 1 : Sat Aug 12 15:38:30 CST 2017

在上面的输出中,打印语句的顺序每次都会不同。我们可以清楚地看到ThreadLocal值为每个线程实例所保管。

最常见的ThreadLocal使用是当您有一些对象不是线程安全的,但您希望避免使用同步关键字/块同步访问该对象。相反,给每个线程自己的对象实例来工作。
一个很好的替代synchronization(同步)或ThreadLocal是使用局部变量。局部变量始终是线程安全的。唯一阻止你这样做的是应用程序设计约束。

在webapp服务器,它可能保持一个线程池,所以一个ThreadLocal变量应该在响应客户端请求前删除,因为当前线程可以被下一个请求重复使用。另外,如果你当你完成请求不清理的时候,任何引用它加载的类将保持在永久堆作为部署webapp的一部分,并永远不会被垃圾回收。

InheritableThreadLocal

InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。
参考
1.Java ThreadLocal Variables – When and How to Use?
2.Java TheadLocal

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容