ThreadLocal(2)——多线程中的ThreadLocal

多线程中的ThreadLocal

大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。从概念上看,我们把ThreadLocal<T>理解成一个包含了Map<Thread,T>的对象,其中Map的key用来标识不同的线程,而Map的value存放了特定该线程的某个值。但是ThreadLocal的实现并非如此,我们以这样的理解方式去使用ThreadLocal也并不能实现真正的线程安全。

这些特定于线程的值是保存在当前的Thread对象中,并非保存在ThreadLocal对象中。并且我们发现Thread对象中保存的是Object对象的一个引用,这样的话,当有其他线程对这个引用指向的对象做修改时,当前线程Thread对象中保存的值也会发生变化。

public class NotSafeThread implements Runnable {

    public static Number number = new Number();

    public static int i = 0;

    public void run() {
        //每个线程计数加一
        number.setNum(i++);
        //将其存储到ThreadLocal中
        value.set(number);
        //延时2秒
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
        }
        //输出num值
        System.out.println(value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new NotSafeThread());
        }
    }

}

运行程序,输出:

4
4
4
4
4

为什么每个线程都输出4?难道他们没有独自保存自己的Number副本吗?为什么其他线程还是能够修改这个值?我们看一下ThreadLocal的源码:

public void set(Object obj)
{
        Thread thread = Thread.currentThread();//获取当前线程
        ThreadLocalMap threadlocalmap = getMap(thread);
        if(threadlocalmap != null)
            threadlocalmap.set(this, obj);
        else
            createMap(thread, obj);
}

其中getMap方法:

ThreadLocal.ThreadLocalMap getMap(Thread thread)
{
   return thread.inheritableThreadLocals; //返回的是thread的成员变量
}

这也就是为什么上面的程序为什么会输出一样的结果:5个线程中保存的是同一Number对象的引用,在线程睡眠2s的时候,其他线程将num变量进行了修改,因此它们最终输出的结果是相同的。

那么,ThreadLocal的 “为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。”这句话中的“独立的副本”,也就是我们理解的“线程本地存储”只能是每个线程所独有的对象并且不与其他线程进行共享,大概是这样的情况:

public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
        public Number initialValue(){//为每个线程保存的值进行初始化操作
            return new Number();
        }
};

或者

public void run() {
        value.set(new Number());
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容