码迷,mamicode.com
首页 > 其他好文 > 详细

理解ThreadLocal

时间:2015-05-22 11:50:38      阅读:144      评论:0      收藏:0      [点我收藏+]

标签:

1.官方介绍

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

要点:

1)每个线程都有自己的局部变量

    每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的(有前提,后面解释)

2)独立于变量的初始化副本

    ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。 

3)状态与某一个线程相关联

    ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要

2.应用举例

ThreadLocalTest
package com.meituan;
 
/**
 * Created by zsq on 15/5/20.
 */
public class ThreadLocalTest {
 
    //创建一个Integer型的线程本地变量
    public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int j = 0; j < 5; j++) {
            threads[j] = new Thread(new Runnable() {
                @Override
                public void run() {
                    //获取当前线程的本地变量,然后累加5次
                    int num = local.get();
                    for (int i = 0; i < 5; i++) {
                        num++;
                    }
                    //重新设置累加后的本地变量
                    local.set(num);
                    System.out.println(Thread.currentThread().getName() + " : " + local.get());
 
                }
            }, "Thread-" + j);
        }
 
        for (Thread thread : threads) {
            thread.start();
        }
    }
}
 

上述程序的运行结果:

 

Thread-0 : 5
Thread-3 : 5
Thread-2 : 5
Thread-1 : 5
Thread-4 : 5

我们看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。

3.查看源码,深入理解

到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:

set() and get()  
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
 
public T get() {
    //获取当前执行线程
    Thread t = Thread.currentThread();
    //取得当前线程的ThreadLocalMap实例
    ThreadLocalMap map = getMap(t);
    //如果map不为空,说明该线程已经有了一个ThreadLocalMap实例
    if (map != null) {
        //map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量
        ThreadLocalMap.Entry e = map.getEntry(this);
        //如果当前线程本地变量存在这个map中,则返回其对应的值
        if (e != null)
            return (T)e.value;
    }
    //如果map不存在或者map中不存在当前线程本地变量,返回初始值
    return setInitialValue();
}
 
private T setInitialValue() {
    //获取初始化值,initialValue 就是我们之前覆盖的方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //如果map不为空,将初始化值放入到当前线程的ThreadLocalMap对象中
    if (map != null)
        map.set(this, value);
    else
        //当前线程第一次使用本地线程变量,需要对map进行初始化工作
        createMap(t, value);
    //返回初始化值
    return value;
}

ThreadLocal有一个内部类ThreadLocalMap,这个类的实现占了整个ThreadLocal类源码的一多半。这个ThreadLocalMap的作用非常关键,它就是线程真正保存线程自己本地变量的容器。每一个线程都有自己的单独的一个ThreadLocalMap实例,其所有的本地变量都会保存到这一个map中。首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

getMap()
//直接返回线程对象的threadLocals属性
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}  

Thread对象都有一个ThreadLocalMap类型的属性threadLocals,这个属性是专门用于保存自己所有的线程本地变量的。这个属性在线程对象初始化的时候为null。所以对一个线程对象第一次使用线程本地变量的时候,需要对这个threadLocals属性进行初始化操作。注意要区别 “线程第一次使用本地线程变量”和“第一次使用某一个线程本地线程变量”。

4.总结 

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

 

理解ThreadLocal

标签:

原文地址:http://my.oschina.net/zouqun/blog/418056

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!