首先再讨论题主的这个观点之前我们要明确一下ThreadLocal的用途是什么?

ThreadLocal并不是用来解决共享对象的多线程访问问题。

看了许多有关ThreadLocal的博客,看完之后会给人一种错觉,ThreadLocal就是用于在多线程情况下防止共享对象的线程安全问题,使用ThreadLocal之后,ThreadLocal的对象就不会有线程安全问题,但是一定是这样么,看如下代码

  1.  
    public class test {
  2.  
    public static void main(String[] args) throws InterruptedException {
  3.  
    new A().start();
  4.  
    new A().start();
  5.  
    new A().start();
  6.  
    new A().start();
  7.  
    }
  8.  
     
  9.  
    static class A extends Thread {
  10.  
    static List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
  11.  
    static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
  12.  
    @Override
  13.  
    protected List<Integer> initialValue() {
  14.  
    return list;
  15.  
    }
  16.  
    };
  17.  
     
  18.  
    @Override
  19.  
    public void run() {
  20.  
    List<Integer> threadList = threadLocal.get();
  21.  
    threadList.add(threadList.size());
  22.  
    System.out.println(threadList.toString());
  23.  
    }
  24.  
     
  25.  
    }
  26.  
    }

该代码很简单,就是在多线程的情况下输出ThreadLocal的list集合状态,如果此时线程安全的话,输出的4个语句应该是完全一样的输出结果如下:

[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5, 6]
[1, 2, 3, 4, 5, 5, 6, 7]

[1, 2, 3, 4, 5, 5, 6, 7, 8]

很明显可以看到,线程是不安全的,从结果也能看出来4个线程中ThreadLocal中的List是同一个对象,被四个线程所共享。

接下来我们分析一下原因的发生原因,我们去看ThreadLocal的get()方法

  1.  
    public T get() {
  2.  
    Thread t = Thread.currentThread();
  3.  
    ThreadLocalMap map = getMap(t);
  4.  
    if (map != null) {
  5.  
    ThreadLocalMap.Entry e = map.getEntry(this);
  6.  
    if (e != null) {
  7.  
    @SuppressWarnings("unchecked")
  8.  
    T result = (T)e.value;
  9.  
    return result;
  10.  
    }
  11.  
    }
  12.  
    return setInitialValue();
  13.  
    }

如果一个线程第一次调用threadLocal.get()方法时,我们通过调试可以发现此时拿到的map是null,会调用setInitialValue(),继续看该方法

  1.  
    private T setInitialValue() {
  2.  
    T value = initialValue();
  3.  
    Thread t = Thread.currentThread();
  4.  
    ThreadLocalMap map = getMap(t);
  5.  
    if (map != null)
  6.  
    map.set(this, value);
  7.  
    else
  8.  
    createMap(t, value);
  9.  
    return value;
  10.  
    }

可以看到这个方法里面有一个value,而这个value就是ThreadLocal中即将要保存的只对线程所见的"副本",而这个value使用过initialValue()方法得到的,这个方法如果你没有重写的话默认返回时一个null,而在我们的例子当中他返回的是我们定义的静态变量list,而这个list是一个单例的(只会被类第一次访问时进行初始化),也就是说我们的例子中每个线程的ThreadLocal里面get的都是同一个list,所以就造成了线程安全的问题.

接下来改善一下我们的代码

  1.  
    static class A extends Thread {
  2.  
    static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
  3.  
    @Override
  4.  
    protected List<Integer> initialValue() {
  5.  
    return new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
  6.  
    }
  7.  
    };
  8.  
     
  9.  
    @Override
  10.  
    public void run() {
  11.  
    List<Integer> threadList = threadLocal.get();
  12.  
    threadList.add(threadList.size());
  13.  
    System.out.println(threadList.toString());
  14.  
    }
  15.  
     
  16.  
    }

运行结果

[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]

[1, 2, 3, 4, 5, 5]

[1, 2, 3, 4, 5, 5]

接下来回到主题,ThreadLocal的作用是什么:

ThreadLocal使得各线程能够保持各自独立的一个对象,而实现原理其实是通过,每个线程都会重新创建一个对象,不是什么对象的拷贝或副本,而线程是否安全取决于你如何去创建这个对象。

然后总结一下:

1.ThreadLocal作用不是为了解决共享对象的多线程安全问题,而是为了避免通多参数传递的方式去拿到一个对象,网上有些例子就一开始拿线程安全举例子,然后抛砖引玉出ThreadLocal,会把人带偏。。。博主就是一个。

2.ThreadLocal中保存的不是什么对象的副本,里面没有需要保存的对象做任何复制,拷贝操作,这个对象完全取决于你的iniialtValue方法中如何去创建,所以这里需要考虑使用ThreadLocal的性能问题,是否会大量创建一个对象。

正确理解ThreadLocal:ThreadLocal中的值并不一定是完全隔离的的更多相关文章

  1. 正确理解Spring AOP中的Around advice

    Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...

  2. 正确理解web交互中的cookie与session

    cookie存储在客户端的纯文本文件 用户请求服务器脚本 脚本设置cookie内容 并 通过http-response发送cookie内容到客户端并保存在客户端本地 客户端再次发送http请求的时候会 ...

  3. 正确理解ThreadLocal

    想必很多朋友对 ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理 解,然后根据ThreadLocal类的 ...

  4. Java_正确理解ThreadLocal

    首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各 ...

  5. java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承

      ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...

  6. IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

    本文引用了简书作者“骑小猪看流星”技术文章“Cookie.Session.Token那点事儿”的部分内容,感谢原作者. 1.前言 众所周之,IM是个典型的快速数据流交换系统,当今主流IM系统(尤其移动 ...

  7. 正确理解DTO、值对象和POCO

    今天推荐的文章比较技术化也比较简单,但是对于一些初学者而言,可能也是容易搞混的概念:就是如何理解DTO.值对象和POCO之间的区别. 所谓DTO就是数据传输对象(Data Transfer Objec ...

  8. C++ : 从栈和堆来理解C#中的值类型和引用类型

    C++中并没有值类型和引用类型之说,标准变量或者自定义对象的存取默认是没有区别的.但如果深入地来看,就要了解C++中,管理数据的两大内存区域:栈和堆. 栈(stack)是类似于一个先进后出的抽屉.它的 ...

  9. clojure中符号symbols 和变量vars的正确理解

    原地址  http://stackoverflow.com/questions/11662084/why-does-clojure-distinguish-between-symbols-and-va ...

随机推荐

  1. JavaPersistenceWithHibernate第二版笔记-第七章-002Mapping an identifier bag(@CollectionId、@ElementCollection、@CollectionTable、@Type)

    一.结构 A bag is an unordered collection that allows duplicate elements, like the java.util.Collection ...

  2. GTK编程

    一.简介 GTK(GIMP Toolkit)是一套跨多种平台的图形工具包,按LGPL许可协议发布的.虽然最初是为GIMP写的,但早已发展为一个功能强大.设计灵活的通用图形库.特别是被GNOME选中使得 ...

  3. java中是如何解决编码问题的,比如char类型的对象是如何存储的呢?

    主题句:每个编码形式将字符从字符集转换为编码数据. 说白了一个代码点就是一个Unicode字符.代码单元就是代码点的集合. 字符视图 要了解字符集标准,您必须能区分三种不同的字符视图: 字符集(字符的 ...

  4. Redis了解

    1. 使用Redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string,li ...

  5. Java 子类初始化过程

    //父类 class Animal{ private String name; private int age; Animal(String name, int age) {//创建父类构造器 sup ...

  6. TCP/IP与套接字

    以前我们讲过进程间通信,通过进程间通信可以实现同一台计算机上不同的进程之间通信. 通过网络编程可以实现在网络中的各个计算机之间的通信. 进程能够使用套接字实现和其他进程或者其他计算机通信. 同样的套接 ...

  7. 跑实验配环境(tensorflow)

    最近在学习用CNN(卷积神经网络)做图像质量评价,选择的论文是CVPR2014-Convolutional neural networks for no-reference image quality ...

  8. C++轮子队-第六周--事后分析

    C++轮子队 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 实现2048+俄罗斯方块结合的小游戏,定义的比较清楚,典型用户也很清晰,提供给那些对该类游 ...

  9. 原来腾讯安全中心TP和虚拟机vmware10是有冲突的

    用VMware 10启动linux,总是出现VMware Workstation 不可恢复错误: (vmx)...   然后网上查了好多类似问题始终不能解决.后来发现每次出现这个问题腾讯的TP是启动的 ...

  10. FPM包定制完成 (等待实现 里程碑 1 和 2) 2018年4月13日 2:18:32

    前期环境准备: 关闭SELINUX  :   setenforce 0 关闭SELINUX  :   sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' / ...