一、前言

  之前ThreadLocal使用不多,有个细节也就注意不到了:ThreadLocal在多线程中到底起什么作用?用它保存的变量在每个线程中,是每个线程都保存一份变量的拷贝吗?带着这些问题,我查了几篇博客,又是一个个“罗生门”,很多都贴了ThreadLocal的源码,但是却有些差异,难道是使用的jar不是同一个版本?了解不多,也不敢妄评谁是谁非,于是自己反编译rt.jar进行查看,再结合自己的实例,最终确认ThreadLocal里面存储的并不是某个变量的副本或者拷贝,假如多个线程中用ThreadLocal存储“共享变量”(比如某个对象的引用),那么并不能保证线程安全,其中某个线程改变了这个“共享变量”,那么其他线程使用这个变量的时候也会受影响~

  再引用一下大神对ThreadLocal下的结论:首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。(出处:http://www.iteye.com/topic/103804)(本文引用的源码来自JDK1.6里面的rt.jar包)。

二、正文

  介绍一下ThreadLocal提供的几个方法(泛型实现是在JDK1.5之后):

  public T get() { }
  public void set(T value) { }
  public void remove() { }
  protected T initialValue() { }

2.1 get方法实现

public T get()
{
Thread localThread = Thread.currentThread();
ThreadLocalMap localThreadLocalMap = getMap(localThread);
if (localThreadLocalMap != null)
{
ThreadLocal.ThreadLocalMap.Entry localEntry = localThreadLocalMap.getEntry(this);
if (localEntry != null)
return localEntry.value;
}
return setInitialValue();
}

  代码首先获取当前线程,并且通过getMap(T)获取ThreadLocalMap(每个线程都维持有一个ThreadLocalMap类型的变量,名为:"threadLocals";ThreadLocalMap是ThreadLocal的一个静态内部类),如果返回的值不为空,那么返回<key,value>键值对中存储的value,注意,这边的key是“this”,是ThreadLocal实例,而不是当前线程!如果为空,那么调用setInitialValue,并且返回value。

  我们再来看看getMap的实现:

  //getMap
  ThreadLocalMap getMap(Thread paramThread)
 {
  return paramThread.threadLocals;
  }

  大家可以看到,在getMap中,获取的是当前线程的成员变量:threadLocals,在Thread里面,这个成员变量的类型如下:

  ThreadLocal.ThreadLocalMap threadLocals = null;

  这边可以看看ThreadLocalMap的内部实现(红色部分特别注意一下,在1.6的rt.jar中,是super(),不知道是不是我的反编译工具有问题,但是我觉得应该是这边写的这种才对):

static class ThreadLocalMap{
static class Entry extends WeakReference<ThreadLocal>{
Object value;
Entry(ThreadLocal paramThreadLocal, Object paramObject){
super(paramThreadLocal);
value=v;
}
}
}

  这边使用到的WeakReference我会在下一篇博客《Java中的“引用”》中说明,敬请期待~

  接下来我们再来看看get()方法中调用的的setInitialValue实现:

//setInitialValue
  private T setInitialValue()
{
  Object localObject = initialValue();
  Thread localThread = Thread.currentThread();
  ThreadLocalMap localThreadLocalMap = getMap(localThread);
  if (localThreadLocalMap != null)
  localThreadLocalMap.set(this, localObject);
  else
  createMap(localThread, localObject);
  return localObject;
 }

  这个代码的第一行可以看到调用了initalValue()方法,这个方法的默认实现是:

protected T initialValue()
{
return null;
}

  很多时候,我们会去重写这个方法,比如这样:

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
}; public static Connection getConnection() {
return connectionHolder.get();
}

  再来看看,setInitialValue中createMap()的实现:

void createMap(Thread paramThread, T paramT)
{
paramThread.threadLocals = new ThreadLocalMap(this, paramT);
}

  通过源码的分析,相信大家对ThreadLocal的内部实现已经有所了解,但是看rt.jar里面的源代码,确实没有看到对存储的值进行复制或者拷贝啊,那网上不是很多人说ThreadLocal里面保存的是“变量的拷贝或者变量的副本吗”?小生看来看去,确实无法理解,如果当ThreadLocal里面放的是一个共享的引用类型的变量,那么在多线程环境下,其中一个线程改变了这个共享引用的值,其他线程是否会受影响呢?于是,我就敲了如下代码:

// Class Jack
package com.test.main;
import java.util.HashMap;
import java.util.Map;
public class Jack {
public static ThreadLocal<Map<String,String>> container = new ThreadLocal<Map<String,String>>();
public static Map<String,String> info = new HashMap<String,String>(){{put("name","Tom");}};
public void setValue(){
container.set(info);
}
}
//MyThread
package com.test.main;
public class MyThread extends Thread {
private Jack jack;
public MyThread(Jack jack,String name){
super(name);
this.jack = jack;
}
public void run(){
jack.setValue();
Thread t = Thread.currentThread();
String name = t.getName();
try{
if("01".equals(name)){
Thread.sleep(2000);
}
else if("02".equals(name)){
Thread.sleep(4000);
}
else{
Thread.sleep(6000);
}
}
catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("线程"+name+":\n原先Jack中name="+jack.container.get().get("name")+"\n"+"现在改为:name="+name+"\n");
jack.container.get().put("name", name);
}
}
//Class MainTest
package com.test.main;
public class MainTest {
public static void main(String[] args) {
Jack jack = new Jack();
Thread t1 = new MyThread(jack,"01");
Thread t2 = new MyThread(jack,"02");
Thread t3 = new MyThread(jack,"03");
t1.start();
t2.start();
t3.start();
}
}

  运行上面MainTest之后的输出如下:

  

  从上面的代码以及运行结果来看,在多线程环境,如果ThreadLocal里面保存的是一个引用类型的变量,还是不能保证线程安全,由此得出如下结论

  首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new对象的操作来创建的对象,每个线程创建一个(这句话怎么理解,可以看本文提到的重写“initialValue”方法部分),不是什么对象的拷贝或副本。

三、参考

  http://www.cnblogs.com/dolphin0520/p/3920407.html
  http://bbs.csdn.net/topics/380049261
  http://blog.csdn.net/cryssdut/article/details/52123142
  http://blog.csdn.net/lufeng20/article/details/24314381
  http://www.iteye.com/topic/103804
  http://blog.csdn.net/zjclugger/article/details/10940927
  http://www.tuicool.com/articles/imyueq
  http://blog.csdn.net/matrix_xu/article/details/8424038

  

  (备注,本人QQ:1163152850,如有问题欢迎交流,暗号:博客园)

ThreadLocal 验明正身的更多相关文章

  1. Struts2值栈

    一.前言 很多事儿啊,就是“成也萧何败也萧何”,细想一些事儿心中有感,当然,感慨和本文毛关系都没有~想起之前有篇Struts2中值栈的博客还未完工,就着心中的波澜,狂咽一把~ 二.正文 博文基于:st ...

  2. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  3. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  4. Threadlocal使用Case

    Threadlocal能够为每个线程分配一份单独的副本,使的线程与线程之间能够独立的访问各自副本.Threadlocal 内部维护一个Map,key为线程的名字,value为对应操作的副本. /** ...

  5. 多线程映射工具——ThreadLocal

    ThreadLocal相当于一个Map<Thread, T>,各线程使用自己的线程对象Thread.currentThread()作为键存取数据,但ThreadLocal实际上是一个包装了 ...

  6. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  7. ThreadLocal<T>的是否有设计问题

    一.吐槽 ThreadLocal<T>明显是.NET从JAVA中来的一个概念,但是这种设计是否出现了问题. 很明显,在JAVA中threadLocal直接是Thread的成员,当然随着th ...

  8. 理解ThreadLocal —— 一个map的key

    作用: 当工作于多线程中的对象使用ThreadLocal维护变量时,threadLocal为每个使用该变量的线程分配一个独立的变量副本. 接口方法: protected T initialValue( ...

  9. JavaSe:ThreadLocal

    JDK中有一个ThreadLocal类,使用很方便,但是却很容易出现问题.究其原因, 就是对ThreadLocal理解不到位.最近项目中,出现了内存泄漏的问题.其中就有同事在使用ThreadLocal ...

随机推荐

  1. vs2017中的scanf_s

    在visual studio 2017中格式化输入函数不同于其他c/c++编译器使用scanf,而是使用scanf_s. scanf_s相比较于scanf来说更安全,因为使用scanf_s函数需要有一 ...

  2. 通过xshell在linux上安装solr4.10.3

    通过xshell在linux上安装solr4.10.3 0)下载linux下的安装包 1)通过xftp6上传到linux上 3)在xshell下依次执行 解压命令:tar xvfz solr.tgz( ...

  3. shiro笔记-"Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - ylw, rememberMe=false]. Possible unexpected error? (Typical or expected login exceptions should ext

    在学习shiro过程中遇到这个错误,在网上找了好久资料也没找到解决办法,大概都是说和传入的值有问题.于是我试着耐心看我自己的报错信息,最终找到了原因并解决.每个人的问题可能都会有差异,所以建议大家耐心 ...

  4. angular、angular2、vue的生命周期

    angular生命周期是什么 1.Angular每个组件都存在一个生命周期,从创建,变更到销毁.Angular提供组件生命周期钩子,把这些关键时刻暴露出来,赋予在这些关键结点和组件进行交互的能力,掌握 ...

  5. http状态码(status_codes)

    首先:1XX 接受的请求正在处理,2XX请求正常处理完毕,3XX需要进行附加操作以完成请求(重定向?),4XX服务器无法处理请求(也就是客户端请求错误),5XX服务器处理请求出错. 当然不仅仅是一张图 ...

  6. Vue2+VueRouter2+webpack+vue-cil构建完整项目实例(附:详细步骤截图)

    引用1:https://segmentfault.com/a/1190000008557578 引用2:https://blog.csdn.net/wulala_hei/article/details ...

  7. thinkphp-PHP实现pdf导出功能

    Thinkphp框架引用tcpdf插件,插件下载地址:待续... 代码编写前先引入tcpdf整个文件夹到项目目录的ThinkPHP文件夹下 如:/ThinkPHP/Library/Vendor/tcp ...

  8. jquery easyui alert闪一下的问题

    最近做项目使用了 jQuery EasyUI,版本是 1.4.3.x,在使用alert方法的时候如果alert后面执行页面跳转的话alert的消息只会闪一下,就跳到其他页面了 $.messager.a ...

  9. 07 json与os模块(进阶)

    json和os模块 阶段一 .数据交换 1.json的基本介绍 JSON全名是JavaScript Object Notation(即:JavaScript对象标记)它是JavaScript的子集. ...

  10. c语言中 *p++ 和 (*p)++ 和 *(p++) 和 *(++p) 和++(*p)和 *(p--)和 *(--p)有什么区别?

    *p++是指下一个地址; (*p)++是指将*p所指的数据的值加一; /******************解释**********************/ ->C编译器认为*和++是同优先级 ...