详细领悟ThreadLocal变量

关于对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

时间: 2024-08-29 10:10:21

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

ThreadLocal变量

什么是ThreadLocal变量?ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量.(1). 通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的:(2). 为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,

java基础-ThreadLocal变量和普通变量的区别

java提供了ThreadLocal这个类型,具有该类型的成员变量,每个使用到该变量的线程都保留一份该属性的备份数据,在线程内部对该属性的操作都是自己备份的数据,所以声明为ThreadLocal类型的成员变量都是线程安全的. 简单测试了一下ThreadLocal类型的成员和普通成员的区别,在多线程环境,每个线程都会存有一个ThreadLocal的值,而普通成员则是线程共享的. import java.util.Date; public class MyThreadLocal { private

Java并发机制(4)--ThreadLocal线程本地变量(转)

转自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920407.html Java并发编程:深入剖析ThreadLocal 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal

线程本地变量ThreadLocal

一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响. 有时,你希望程序里的各个线程的属性不会被共享. Java 并发 API提供了一个很清楚的机制叫本地线程变量即ThreadLocal. 模拟ThreadLocal类实现:线程范围内的共享变量,每个线程只能访问他自己的,不能访问别的

深入理解线程本地变量ThreadLocal

ThreadLocal理解: 如果在多线程并发环境中,一个可变对象涉及到共享与竞争,那么该可变对象就一定会涉及到线程间同步操作,这是多线程并发问题. 否则该可变对象将作为线程私有对象,可通过ThreadLocal进行管理,实现线程间私有对象隔离的目的. 可以发现,ThreadLocal并没有解决多线程并发的问题,因为ThreadLocal管理的可变对象的性质本来就不会涉及到多线程并发而引发的共享.竞争和同步问题,使用ThreadLocal管理只是方便了多线程获取和使用该私有可变对象的途径和方式.

ThreadLocal 与 static 变量

ThreadLocal是为解决多线程程序的并发问题而提出的,可以称之为线程局部变量.与一般的变量的区别在于,生命周期是在线程范围内的.static变量是的生命周期与类的使用周期相同,即只要类存在,那么static变量也就存在.那么一个 static 的 ThreadLocal会是什么样的呢? 看下面一个例子, [java] view plain copy public class SequenceNumber { private static ThreadLocal<Integer> seqN

线程变量(ThreadLocal)的使用和测试

ThreadLocal可以定义线程范围的变量,也可以称之为线程局部变量.与一般的变量的区别在于,生命周期是在线程范围内的. 也就是说某个类的某个对象(为清晰描述,以下称A对象)里面有个ThreadLocal变量, 那么每开一个线程,在线程中第一次调用A对象,都会先初始化该变量的值,并且不会对其他线程中的A对象产生影响.测试如下: public interface IBase { public static final ThreadLocal<Boolean> onlyCallLocalFlg

菜鸟之路——Java并发之ThreadLocal

一.什么是ThreadLocal ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal的目的是为了解决多线程访问资源时的共享问题.但其实这么说并不准确.ThreadLocal是为变量在每个线程中都创建了一个副本(此副本的意思是通过每个线程中的new操作来创建内容一样的新的对象,每个线程创建一个,而不是使用对象的引用),使每个线程可以访问自己内部的副

【ThreadLocal】深入JDK源码之ThreadLocal类

学习JDK中的类,首先看下JDK API对此类的描述,描述如下: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal其实就是一个工具类,用来操作线程局部变量,ThreadLocal 实例通常是类中的 private static 字段.它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联. 例如,以下类生成对每个线程