ThreadLocal学习

在学习ThreadLocal时遇到了问题,然后再结合各方面资料和实践才解决。下面重现问题及解决过程,愿于诸君共勉。

ThreadLocal官方说明

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread
我纯属英盲,只好找百度翻译一下:
该类提供线程本地变量。这些变量与一般的变量不同,每个线程访问一个线程(通过get或set方法)有自己独立的变量初始化副本。ThreadLocal实例通常是私有的静态字段在类希望关联状态的线程。
百度翻译感觉很拗口,那么怎么理解呢?看set和get方法就知道其是存储在map中的,而key是当前线程。确保每次取值都是在当前线程内有效。当前线程这个作用域有点奇怪。下面例子中将讲到,在线程new时,start()之前,在其构造方法内取得Thread.currentThread()的值居然不是新建的这个线程。
总结:ThreadLocal提供的变量相当于该线程的私有变量,其他线程访问不到。从而实现线程安全。

Set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

Get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            return (T)map.get(this);
        T value = initialValue();
        createMap(t, value);
        return value;
    }

教材示例代码出自《Java特种兵》

public class ThreadLocalTest {

        static class ResourceClass{
            public final static ThreadLocal<String> RESOURCE_1=new ThreadLocal<String>();
            public final static ThreadLocal<String> RESOURCE_2=new ThreadLocal<String>();
        }

        static class A{
            public void setOne(String value){
                ResourceClass.RESOURCE_1.set(value);
            }
            public void setTow(String value){
                ResourceClass.RESOURCE_2.set(value);
            }
        }
        static class B{
            public void display(){
                System.out.println(ResourceClass.RESOURCE_1.get()+":"+ResourceClass.RESOURCE_2.get());
            }
        }
        public static void main(String[] args) {
            final A a=new A();
            final B b=new B();
            for(int i=0;i<15;i++){
                final String r1="线程-"+i;
                final String r2=" value="+i;
                new Thread(){
                    public void run(){
                        try{
                            a.setOne(r1);
                            a.setTow(r2);
                            b.display();
                        }finally{
                            ResourceClass.RESOURCE_1.remove();
                            ResourceClass.RESOURCE_2.remove();
                        }
                    }
                }.start();
            }
        }

    }

运行结果
这里写图片描述
按教材上的代码没问题。感觉只有在遇到问题并解决了才会记住。不然很容易忘记。
教材示例用的是匿名线程,我想改成like me 这样的菜鸡好理解的代码。于是乎问题来了。
修改教材的代码如下:

public class ThreadLocalTest {

    static class ResourceClass{
        public final static ThreadLocal<String> RESOURCE_1=new ThreadLocal<String>();
        public final static ThreadLocal<String> RESOURCE_2=new ThreadLocal<String>();
    }

    static class A{
        public void setOne(String value){
            ResourceClass.RESOURCE_1.set(value);
        }
        public void setTow(String value){
            ResourceClass.RESOURCE_2.set(value);
        }
    }
    static class B{
        public void display(){
            System.out.println(ResourceClass.RESOURCE_1.get()+":"+ResourceClass.RESOURCE_2.get());
        }
    }
    static class TestThread extends Thread{
        private final A a;
        private final B b;
        private final String str1;
        private final String str2;
        public TestThread(String str1,String str2){
            this.a=new A();
            this.b=new B();
            this.str1=str1;
            this.str2=str2;
            this.a.setOne(str1);
            this.a.setTow(str2);
        }
        public void run(){
            try{
                this.b.display();
            }finally{
//              ResourceClass.RESOURCE_1.remove();
//              ResourceClass.RESOURCE_2.remove();
            }
        }
    }
    public static void main(String[] args) {
        final A a=new A();
        final B b=new B();
        TestThread tests[]=new TestThread[15];
        for(int i=0;i<15;i++){
            final String r1="线程-"+i;
            final String r2=" value="+i;
            tests[i]=new TestThread(r1,r2);
//          new Thread(){
//              public void run(){
//                  try{
//                      a.setOne(r1);
//                      a.setTow(r2);
//                      Thread t = Thread.currentThread();
//                      b.display();
//                  }finally{
//                      ResourceClass.RESOURCE_1.remove();
//                      ResourceClass.RESOURCE_2.remove();
//                  }
//              }
//          }.start();
//          
        }
        int i=0;
        //debug模式是先全部start完才开始跑run;
        while(i<15){
            tests[i++].start();
        }
    }

}

感觉完全没有问题啊,但是运行结果
这里写图片描述
嗷嗷嗷,怎么会这样,why???
于是注意到教材中的a.setOne(r1);是在run里边的,为什么放在构造方法就不行了呢。debug看了下,运行都构造方法时数据很正常,但是run()打印就是null。于是又在ThreadLocal的set()和get()方法中打断点跟踪。终于发现了问题。set()方法和get()方法取得的当前线程引用不一样(Thread.currentThread())。
于是乎在构造方法和run()方法都添加Thread t = Thread.currentThread();

public TestThread(String str1,String str2){
            this.a=new A();
            this.b=new B();
            this.str1=str1;
            this.str2=str2;
            this.a.setOne(str1);
            this.a.setTow(str2);
            Thread t = Thread.currentThread();
        }
public void run(){
            try{
                this.b.display();
                Thread t = Thread.currentThread();
            }finally{
//              ResourceClass.RESOURCE_1.remove();
//              ResourceClass.RESOURCE_2.remove();
            }
        }

Debug模式启动查看一下Thread.currentThread();
构造方法Thread.currentThread()是Thread[main,5,main]
这里写图片描述
而run()里面的Thread.currentThread()Thread[Thread-1,5,main]
这里写图片描述
同一线程中居然不一样?好慌啊,肿么这样。宝宝很伤心。
那么直接换成单线程看看构造方法中的Thread.currentThread()和run方法中是否一致。

        final String r1="线程-1";
        final String r2=" value=1";
        TestThread testT=new TestThread(r1,r2);
        testT.start();

这里写图片描述
这里写图片描述
单线程也是一样的结果,说明构造方法中取得的Thread.currentThread()就是mian线程的引用。
Why???仔细想想就知道线程在创建时是在mian线程里创建的,所以获取当前线程的引用是main。
只有当线程开始执行之后才会取得该线程的引用。通过添加this.getName()可以取得线程的Name。
这里写图片描述
这样就可以解释为啥会出现一堆null了。构造方法中调用ThreadLocal的set方法是算在main线程名下的,所以run()方法get不到值。
怎么样才能得到我们希望的结果呢?只要在线程运行之后调用ThreadLocal的set方法就ok啦。比如在run方法中。

调整示例代码

将构造方法中的set放到run()里边

public void run(){
            try{
                this.a.setOne(str1);
                this.a.setTow(str2);
                Thread t = Thread.currentThread();
                this.b.display();
            }finally{
                ResourceClass.RESOURCE_1.remove();
                ResourceClass.RESOURCE_2.remove();
            }
        }

来看看运行结果
这里写图片描述
感觉和教材中示例代码出来的结果一致。

总结一下

ThreadLocal将数据存储在一个map中,并以当前线程的引用作为key。要取出值就只要当前线程可以取出来。所以说ThreadLocal提供的变量相当于该线程的私有变量,其他线程访问不到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值