关于对ThreadLocal变量的理解,我今天查看一下午的博客,自己也写了demo来测试来看自己的理解到底是不是那么回事。从看到博客引出不解,到仔细查看ThreadLocal源码(JDK1.8),我觉得我很有必要记录下来我这大半天的收获,
今天我研究的最多的就是这两篇文章说理解。我在这里暂称为A文章和B文章。以下是两篇博文地址,我是在看完A文章后,很有疑问,特别是在A文章后的各位网页的评论中,更加坚定我要弄清楚ThreadLocal到底是怎么一回事。
A文章:http://blog.csdn.net/lufeng20/article/details/24314381
B文章:http://www.cnblogs.com/dolphin0520/p/3920407.html

首先,我们从字面上的意思来理解ThreadLocal,Thread:线程,这个毫无疑问。那Local呢?本地的,局部的。也就是说,ThreadLocal是线程本地的变量,只要是本线程内都可以使用,线程结束了,那么相应的线程本地变量也就跟随着线程消失了。

以下内容是个人参考他人文章,理解总结出来,偏差之处,欢迎指正。

全篇包括两个部分,我希望大家对ThreadLocal源码已经有一定了解,我在文章中没有具体分析源码:

第一部分是说明ThreadLocal不是用来做变量共享的。

第二部分是深入了解ThreadLocal后得到的结论,谈谈什么情况用ThreadLocal,以及用ThreadLocal有什么好处。

一、ThreadLocal不是用来解决多线程下访问共享变量问题的

我想大家都知道,多线程情况下,对共享变量的访问是需要同步的,不然会引起不可预知的问题。

接下来我就是,我极力想要说明的:ThreadLocal不是用来解决这个问题的!!!!! ThreadLocal可以在本线程持有一个共享变量的副本,对吧。大家都这么说。

我举个栗子,若是在线程的ThreadLocal中set一个程序中唯一的共享变量,该ThreadLocal仅仅是保存了一个共享变量的引用值,共享变量的实例对象在内存中只有一个。

下面我们先测试一下,是不是这样:
我先定义一个Person类,我们假定这个Person是要被共享的吧···哈哈(TheradLocal实际上不是这样用的)

class Person {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

然后创建一个target实现Runnable接口:

/**
* Person 是共享变量
* @author bubble
*
*/
class Target implements Runnable {
private static Person person = new Person("张三");
public Target() {} @Override
public void run() {
//线程中创建一个ThreadLocal变量,并将共享变量创建一个本线程副本
ThreadLocal<Person> df = new ThreadLocal<Person>();
df.set(person);
//对本线程副本中的值进行改变
df.get().setName("李四");
System.out.println("线程" + Thread.currentThread().getName() + "更改ThreadLocal中Person的名字为:" + df.get().getName());
} public Person getPerson() {
return person;
}
}

最后我们来测试一下,到底线程中,对共享变量的本地副本是怎么一回事:

public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
Target target = new Target();
Thread thread = new Thread(target);
thread.start(); //创建一个线程,改变线程中共享变量的值
t1.join(); //等待线程执行完毕
//主线程访问共享变量,发现Person的值被改变
System.out.println("线程" + Thread.currentThread().getName() + "中共享变量Person的名字:" + target.getPerson().getName());
}
}

我们来看看运行结果:

我们可以看到,Thread-0线程虽然创建了一个ThreadLocal,并且将共享变量放入,但是线程内改变了共享变量的值,依然会对共享变量本身进行改变。

参考源码,我们可以看到ThreadLocal调用set(T value)方法时,是将调用者ThreadLocal作为ThreadLocalMap的key值,value作为ThreadLocalMap的value值。

我们看看ThradLocal类里面到底有什么:

红色箭头标注出了四个我们常用的方法,并且ThreadLocal里定义了一个内部类ThreadLocalMap,但是注意一下,虽然它定义了这样一个内部类,但ThreadLocal本身真的没有持有ThreadLocalMap的变量,

这个ThreadLocalMap的持有者是Thread。

所以,文章A中,在开头说了这样一段:

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

正确应该是:在Thread类里面有一个ThreadLocalMap,用于存储每一个线程的变量的引用,这个Map中的键为ThreadLocal对象,而值对应的是ThreadLocal通过set放进去的变量引用。

我在这里一直强调的是,ThreadLocal通过set(共享变量)然后再通过ThreadLocal方法get的是共享变量的引用!!!  如果多个线程都在其执行过程中将共享变量加入到自己的ThreadLocal中,那就是每个线程都持有一份共享变量的引用副本,注意是引用副本,共享变量的实例只有一个。所以,ThreadLocal不是用来解决线程间共享变量的访问的事儿的。想要控制共享变量在多个线程之间按照程序员想要的方式来进行,那是锁和线程间通信的事,和ThreadLocal没有半毛钱的关系。

总的来说:每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程执行期间都可以正确的访问到自己的对象。

二、ThreadLocal到底该怎么用

说了这么多,我觉得还是举栗子来说明一下,ThreadLocal到底该怎么用,有什么好处。

大家都知道,SimpleDateFomat是线程不安全的,因为里面用了Calendar 这个成员变量来实现SimpleDataFormat,并且在Parse 和Format的时候对Calendar 进行了修改,calendar.clear(),calendar.setTime(date)。总之在多线程情况下,若是用同一个SimpleDateFormat是要出问题的。那么问题来了,为了线程安全,是不是在每个线程使用SimpleDateFormat的时候都手动new出来一个新的用?  这得多麻烦啊,一般来说,在开发时,SimpleDateFormat这样的类我们是放在工具类里面的,阿里巴巴Java开发手册里面这样推荐DateUtils:

public class DateUtils {
public static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
}

这里重写了initialValue方法,新建了一个SimpleDateFormat对象并返回,这样我们就可以在任何线程任何地方想要执行日期格式化的时候,就可以像如下方式来执行,并且线程之间互相没有影响:

 DateUtils.df.get().format(new Date());

我们来看看为什么这么做线程之间就没有影响了。假设现在有线程A和线程B同时执行以上语句,那么两个线程是怎么操作的呢?

线程A:df.get的时候,首先尝试获得线程A自己ThreadLocalMap,如果是第一次get,由于我们没有set,而是重写了initialValue方法,所以在A线程第一次get时没有ThreadLocalMap,这时线程A会

new一个线程A自己的ThreadLocalMap出来,将df(注意df是ThreadLocal变量)作为这个map的键,将initialValue中返回的值(注意是new出来的)作为map的值。这个时候A线程里面就有一个ThreadLocalMap了,并且里面保存了一个SimpleDateFormat的引用。那么从现在开始,线程A的生存期间,再次调用df.get(),都将获得一个A线程的ThreadLocalMap,并且通过df作为键得到相应的SimpleDateFormat;

线程B:df.get的时候,首先尝试获得线程B自己ThreadLocalMap,如果是第一次get,由于我们没有set,而是重写了initialValue方法,所以在B线程第一次get时没有ThreadLocalMap,这时线程B会

new一个线程B自己的ThreadLocalMap出来,将df(注意df是ThreadLocal变量,这里的df和线程A中的df是同一个,但是又有什么关系呢,map不一样)作为这个map的键,将initialValue中返回的值(注意是new出来的,这里是线程B在执行df.get时自己new出来的,不再是线程A中的那个了)作为map的值。这个时候A线程里面就有一个ThreadLocalMap了,并且里面保存了一个SimpleDateFormat的引用。那么从现在开始,线程B的生存期间,再次调用df.get(),都将获得一个B线程的ThreadLocalMap,并且通过df作为键得到相应的SimpleDateFormat(这里和线程A中已经是另外一个不同的对象了);

这下大概明白为什么说这样用就线程安全了吧,这里的线程安全并不是指访问的同一个对象,而是每个线程创建自己的对象(SimpleDateFormat)来用,各自用各自的,当然线程安全了。。。

当然大家可以说,这和自己在线程里面每次用的时候new出来一个有什么区别呢,对,没区别,但是这样方便啊,而且可以保持线程里面只有唯一一个SimpleDateFormat对象,你要每用一次new一次,那就消耗内存了撒。可能你会说,那我只new一个,那个方法用的时候通过参数传递过去就行。。。。。  不嫌麻烦的话我也无话可说。哈哈。。  然而ThreadLocal却太方便了。。。   敬仰神人竟然能创造出ThreadLocal。这才是ThreadLocal。

总结一下:

ThreadLocal真的不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。 
1、每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
2、将一个共用的ThreadLocal静态实例作为key(上面得df),将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程生命周期内执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象(指的是SimpleDateFormat)作为参数传递的麻烦。

补充一下:

一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

若不用DateUtils工具类,完全可以在线程开始的时候这样执行:

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
ThreadLocal<SimpleDateFormat> df = new ThreadLocal<>();
df.set(sdf);

然后在线程生命周期的任何地方调用:

 df.get().format(new Date());

效果是一样的,可是这没有工具类方便嘛。。。

本文个人理解后整理,文章中存在很多表述不清楚的地方,欢迎留言讨论。

参考文章:

A文章:http://blog.csdn.net/lufeng20/article/details/24314381
 B文章:http://www.cnblogs.com/dolphin0520/p/3920407.html

C文章:http://www.iteye.com/topic/103804

详细领悟ThreadLocal变量的更多相关文章

  1. Linux多线程编程详细解析----条件变量 pthread_cond_t

    Linux操作系统下的多线程编程详细解析----条件变量 1.初始化条件变量pthread_cond_init #include <pthread.h> int pthread_cond_ ...

  2. ThreadLocal变量

    什么是ThreadLocal变量?ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副 ...

  3. 关于ThreadLocal变量的一个坑

    每个线程都有一个ThreadLocalMap对象,ThreadLocalMap是Thread的一个内部类,可以把ThreadLocalMap理解成一个Map,这个Map里存放这一个Thread的所有线 ...

  4. python中Threadlocal变量

    在多线程环境下,每个线程都有自己的数据.一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁. 不加锁就会出现变量会被修改的问题,进而 ...

  5. Volatile关键字和ThreadLocal变量的简单使用

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11812459.html package thread; /** * volatile关键字和T ...

  6. threadlocal 变量 跟synchronized 关键字的关系

    为什么叫threadloca变量呢,经过大量的查资料发现threadlocal并不是之前理解的控制线程用的东西,它其实也属于一类变量,只不过是线程的局部变量,它的作用就是实现线程间对该变量的唯一线程调 ...

  7. 什么是 ThreadLocal 变量?

    ThreadLocal 是 Java 里一种特殊的变量.每个线程都有一个 ThreadLocal 就是每 个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了.它是为创建代价 高昂的对象获取线程安全 ...

  8. TPS04-J. 使用线程池时确保ThreadLocal变量每次都初始化

    线程池可以提供这种保障,一旦你的代码开始执行了,被分配来执行这个task的线程在执行完你的task之前不会做别的事情. 所以不用担心执行到一半被别的task改了 thread local 的变量. 由 ...

  9. Linux操作系统下的多线程编程详细解析----条件变量

    条件变量通过允许线程阻塞和等待另一个线程发送信号的方法,弥补了互斥锁(Mutex)的不足. 1.初始化条件变量pthread_cond_init #include <pthread.h> ...

随机推荐

  1. 封装 INI 文件读写函数

    delphi读写ini文件实例 //--两个过程,主要实现:窗体关闭的时候,文件保存界面信息:窗体创建的时候,程序读取文件文件保存的信息. //--首先要uses IniFiles(单元) //--窗 ...

  2. redhat6.4下安装Oracle11g

    一.在Root用户下执行以下步骤: 1)修改用户的SHELL的限制,修改/etc/security/limits.conf文件 *               soft    nproc  2047 ...

  3. Java学习之旅基础知识篇:数据类型及流程控制

    经过开篇对Java运行机制及相关环境搭建,本篇主要讨论Java程序开发的基础知识点,我简单的梳理一下.在讲解数据类型之前,我顺便提及一下Java注释:单行注释.多行注释以及文档注释,这里重点强调文档注 ...

  4. MyBatis CRUD Java POJO操作

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...

  5. jQuery实践-别踩白块儿网页版

    ▓▓▓▓▓▓ 大致介绍 终于结束了考试,放假回家了.这次的别踩白块儿网页版要比之前做的 jQuery实践-网页版2048小游戏 要简单一点,基本的思路都差不多. 预览:别踩白块网页版 这篇博客并不是详 ...

  6. Bootstrap入门(五)表单

    Bootstrap入门(五)表单   先引入本地的CSS文件  <link href="css/bootstrap.min.css" rel="stylesheet ...

  7. 史上最全的synchronized解释

    首先:推荐使用synchronized(obj)这种方法体的使用方式,一个类里面建议尽量使用单一的同步方法,多种方法混用,维护成本太大. 其次:关于java5.0新增的ReenTrantLock方法: ...

  8. PHP使用hash_algos函数计算哈希值,之间的性能排序

    PHP从5.1.2版本以上开始支持hash_algos函数,看这个名字就知道了,algos在英文中也表示算法的意思,hash_algos就是哈希算法,收集了一些常用的哈希算法,从5.1.2开始不同版本 ...

  9. js精要之继承

    // 继承object.prototype的方法 // hasOwnProperty() //检查是否存在一个给定名字的自有属性 // propertyIsEnumerable() // 检查一个自有 ...

  10. CTF入门指南

    转自http://www.cnblogs.com/christychang/p/6032532.html ctf入门指南 如何入门?如何组队? capture the flag 夺旗比赛 类型: We ...