ThreadLocal的原理和用法

本篇内容介绍了“ThreadLocal的原理和用法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

创新互联专注于中小企业网站建设、策划制作、运行维护,主要提供一站式的企业网站建设服务。建站类型:公司网站建设、品牌网站建设、成都外贸网站制作独立站等。创新互联不是单一的建网站,而是结合企业的建站目标去规划网站怎么建,如何利于运营,寻求适合的建站方案。其次,网站后台操作的便捷性也是网站制作过程中的重点,创新互联的网站后台简单便捷,真正实现了零基础操作。

ThreadLocal简介

从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个自己独立的变量副本

方法简洁干练,类信息以及方法列表如下:

ThreadLocal的原理和用法

示例

在测试类中定义了一个ThreadLocal变量,用于保存String类型数据 创建了两个线程,分别设置值,读取值,移除后再次读取

package com.declan.threadlocal;

/**
 * @author Declan
 * @date 2019/08/16 14:36
 */
public class ThreadLocalDemo {

    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {

        Thread thread1 = new Thread(()-> {
            //thread1中设置值
            threadLocal.set("this is thread1's local");
            //获取值
            System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
            //移除值
            threadLocal.remove();
            //再次获取
            System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());
        }, "thread1");

        Thread thread2 = new Thread(() -> {
            //thread1中设置值
            threadLocal.set("this is thread2's local");
            //获取值
            System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
            //移除值
            threadLocal.remove();
            //再次获取
            System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());

        }, "thread2");

        //启动两个线程
        thread1.start();
        thread2.start();
    }
}

结果

thread2: threadLocal value:this is thread2's local
thread2: after remove threadLocal value:null
thread1: threadLocal value:this is thread1's local
thread1: after remove threadLocal value:null

从结果可以看得到,每个线程中可以有自己独有的一份数据,互相没有影响remove之后,数据被清空

从上面示例也可以看出来一个情况:

如果两个线程同时对一个变量进行操作,互相之间是没有影响的,换句话说,这很显然并不是用来解决共享变量的一些并发问题,比如多线程的协作

因为ThreadLocal的设计理念就是共享变私有,都已经私有了,还谈啥共享? 比如之前的消息队列,生产者消费者的示例中final LinkedList<Message> messageQueue = new LinkedList<>();如果这个LinkedList是ThreadLocal的,生产者使用一个,消费者使用一个,还协作什么呢?

但是共享变私有,如同并发变串行,或许适合解决一些场景的线程安全问题,因为看起来就如同没有共享变量了,不共享即安全,但是他并不是为了解决线程安全问题而存在的

实例分析

Thread中有一个threadLocals变量,类型为ThreadLocal.ThreadLocalMap

ThreadLocal的原理和用法

ThreadLocalMap则是ThreadLocal的静态内部类,他是一个设计用来保存thread local 变量的自定义的hash map

ThreadLocal的原理和用法

也就是说在Thread中有一个“hashMap”可以用来保存键值对。

set方法

ThreadLocal的原理和用法

在这个方法中,接受参数,类型为T的value

首先获取当前线程,然后调用getMap(t)

ThreadLocal的原理和用法

这个方法很简单,就是直接返回Thread内部的那个“hashMap”(threadLocals是默认的访问权限)

继续回到set方法,如果这个map不为空,那么以this为key,value为值,也就是ThreadLocal变量作为key

如果map为空,那么进行给这个线程创建一个map ,并且将第一组值设置进去,key仍旧是这个ThreadLocal变量

ThreadLocal的原理和用法

简言之:

调用一个ThreadLocal的set方法,会将:以这个ThreadLocal类型的变量为key,参数为value的这一个键值对,保存在Thread内部的一个“hashMap”中

get方法

在get方法内部仍旧是获取当前线程的内部的这个“hashMap”,然后以当前对象this(ThreadLocal)作为key,进行值的获取:

ThreadLocal的原理和用法

我们对这两个方法换一个思路理解:

每个线程可能运行过程中,可能会操作很多的ThreadLocal变量,那么怎么区分各自?

直观的理解就是,我们想要获取某个线程的某个ThreadLocal变量的值

一个很好的解决方法就是借助于Map进行保存,ThreadLocal变量作为key,local值作为value

假设这个map名为:threadLocalsMap,可以提供setter和getter方法进行设置和读取,内部为:

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

这样就可以达到thread --- local的效果,但是是否存在一些使用不便?我们内部定义的是ThreadLocal变量,但是只是用来作为key的?是否直接通过ThreadLocal进行值的获取更加方便呢?

怎么能够做到数据读取的倒置?因为毕竟值的确是保存在Thread中的

其实也很简单,只需要内部进行转换就好了,对于下面两个方法,我们都需要 ThreadLocal 作为key

  • threadLocalsMap.set(ThreadLocal key,T value)

  • threadLocalsMap.get(ThreadLocal key)

而这个key不就是这个ThreadLocal,不就是this 么

所以:

  • ThreadLocal.set(T value)就内部调用threadLocalsMap.set(this,T value)

  • ThreadLocal.get()就内部调用threadLocalsMap.get(this)

所以总结下就是:

  • 每个Thread内部保存了一个"hashMap",key为ThreadLocal,这个线程操作了多少个ThreadLocal,就有多少个key

  • 你想获取一个ThreadLocal变量的值,就是ThreadLocal.get(),内部就是hashMap.get(this);

  • 你想设置一个ThreadLocal变量的值,就是ThreadLocal.set(T value),内部就是hashMap.set(this,value);

关键只是在于内部的这个“hashMap”,ThreadLocal只是读写倒置的“壳”,可以更简洁易用的通过这个壳进行变量的读写,“倒置”的纽带,就是getMap(t)方法.

remove方法

ThreadLocal的原理和用法

remove方法很简单,当前线程,获取当前线程的hashMap,remove

初始值

再次回头看下get方法,如果第一次调用时,指定线程并没有threadLocals,或者根本都没有进行过set,会发生什么?

如下图所示,会调用setInitialValue方法:

ThreadLocal的原理和用法

在setInitialValue方法中,会调用initialValue方法获取初始值,如果该线程没有threadLocals那么会创建,如果有,会使用这个初始值构造这个ThreadLocal的键值对,简单说,如果没有set过(或者压根内部的这个threadLocals就是null的),那么她返回的值就是初始值

ThreadLocal的原理和用法

这个内部的initialValue方法默认的返回null,所以一个ThreadLocal如果没有进行set操作,那么初始值为null:

ThreadLocal的原理和用法

如何进行初始值的设定?

可以看得出来,这是一个protected方法,所以返回一个覆盖了这个方法的子类不就好了?在子类中实现初始值的设置。

总结

通过set方法可以进行值的设定

通过get方法可以进行值的读取,如果没有进行过设置,那么将会返回null;如果使用了withInitial方法提供了初始值,将会返回初始值

通过remove方法将会移除对该值的写入,再次调用get方法,如果使用了withInitial方法提供了初始值,将会返回初始值,否则返回null

对于get方法,很显然如果没有提供初始值,返回值为null,在使用时是需要注意不要引起NPE异常

ThreadLocal,thread local,每个线程一份,到底是什么意思?

他的意思是对于一个ThreadLocal类型变量,每个线程有一个对应的值,这个值的名字就是ThreadLocal类型变量的名字,值是我们set进去的变量, 但是如果set设置的是共享变量,那么ThreadLocal其实本质上还是同一个对象不是么?

这句话如果有疑问的话,可以这么理解:

对于同一个ThreadLocal变量a,每个线程有一个map,map中都有一个键值对,key为a,值为你保存的值,但是这个值,到底每个线程都是全新的?还是使用的同一个?这是你自己的问题了!!!

ThreadLocal可以做到每个线程有一个独立的一份值,但是你非得使用共享变量将他们设置成一个,那ThreadLocal是不会保障的。 这就好比一个对象,有很多引用指向他,每个线程有一个独立的引用,但是对象根本还是只有一个。所以,从这个角度更容易理解,为什么说ThreadLocal并不是为了解决线程安全问题而设计的,因为他并不会为线程安全做什么保障,他的能力是持有多个引用,这多个引用是否能保障是多个不同的对象,你来决策!

所以我们最开始说的,ThreadLocal会为每个线程创建一个变量副本的说法是不严谨的, 是他有这个能力做到这件事情,但是到底是什么对象,还是要看你set的是什么,set本身不会对你的值进行干涉

应用场景

前面说过,对于之前生产者消费者的示例中,就不适合使用ThreadLocal,因为问题模型就是要多线程之间协作,而不是为了线程安全就将共享变量私有化

比如,银行账户的存款和取款,如果借助于ThreadLocal创建了两个账户对象,就会有问题的,初始值500,明明又存进来1000块,可支配的总额还是500

那ThreadLocal适合什么场景呢? 既然是每个线程一个,自然是适合那种希望每个线程拥有一个的那种场景(好像是废话)

一个线程中一个,也就是线程隔离,既然是一个线程一个,那么同一个线程中调用的方法也就是共享了,所以说,有时,ThreadLocal会被用来作为参数传递的工具

因为它能够保障同一个线程中的值是唯一的,那么他就共享于所有的方法中,对于所有的方法来说,相当于一个全局变量了!

所以可以用来同一个线程内全局参数传递

示例

对于JavaWeb项目,大家都了解过Session

ps:此处不对session展开介绍,打开浏览器输入网址,这就会建立一个session,关闭浏览器,session就失效了

在这个时间段内,一个用户的多个请求中,共享同一个session,Session 保存了很多信息,有的需要通过 Session 获取信息,有些又需要修改 Session 的信息。

每个线程需要独立的session,而且很多地方都需要操作 Session,存在多方法共享 Session 的需求,所以session对象需要在多个方法中共享

如果不使用 ThreadLocal,可以在每个线程内创建一个 Session对象,然后在多个方法中将他作为参数进行传递

很显然,如果每次都显式的传递参数,繁琐易错

这种场景就适合使用ThreadLocal

下面的示例就模拟了多方法共享同一个session,但是线程间session隔离的示例:

package com.declan.threadlocal;


/**
 * @author Declan
 * @date 2019/08/16 16:07
 */
public class SessionDemo {

    /**
     * session变量定义
     */
    static ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>();


    /**
     * 获取session
     * @return
     */
    public static Session getSession(){
        if(sessionThreadLocal.get() == null){
            sessionThreadLocal.set(new Session());
        }
        return sessionThreadLocal.get();
    }

    /**
     * 移除session
     */
    public static void closeSession(){
        sessionThreadLocal.remove();
    }

    /**
     * 模拟一个调用session的方法1
     */
    public static void fun1(Session session){

    }

    /**
     * 模拟一个调用session的方法2
     */
    public static void fun2(Session session){

    }

    /**
     * 模拟session对象
     */
    static class Session{

    }

    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            fun1(sessionThreadLocal.get());
            fun2(sessionThreadLocal.get());
            closeSession();
        });
        thread.start();
    }
}

所以,ThreadLocal最根本的使用场景应该是:

在每个线程希望有一个独有的变量时(这个变量还很可能需要在同一个线程内共享),避免每个线程还需要主动地去创建这个对象(如果还需要共享,也一并解决了参数来回传递的问题), 换句话说就是,“如何优雅的解决:线程间隔离与线程内共享的问题”,而不是说用来解决乱七八糟的线程安全问题。

所以说如果有些场景你需要线程隔离,那么考虑ThreadLocal,而不是你有了什么线程安全问题需要解决,然后求助于ThreadLocal,这不是一回事

再次注意:

ThreadLocal只是具有这样的能力,是你能够做到每个线程一个独有变量,但是如果你set时,不是传递的new出来的新变量,也就只是理解成“每个线程不同的引用”,对象还是那个对象(有点像参数传递时的值传递,对于对象传递的就是引用)

总结

ThreadLocal可以用来优雅的解决线程间隔离的对象,必须主动创建的问题,借助于ThreadLocal无需在线程中显式的创建对象,解决方案很优雅

ThreadLocal中的set方法并不会保障的确是每个线程会获得不同的对象,你需要对逻辑进行一定的处理(比如上面的示例中的getSession方法,如果ThreadLocal 变量的get为null,那么new对象) 是否真的能够做到线程隔离,还要看你自己的编码实现,不过如果是共享变量,你还放到ThreadLocal中干嘛?

所以通常都是线程独有的对象,通过new创建。

“ThreadLocal的原理和用法”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!

当前文章:ThreadLocal的原理和用法
当前URL:https://www.cdcxhl.com/article32/gocppc.html

成都网站建设公司_创新互联,为您提供营销型网站建设商城网站品牌网站建设网站制作网页设计公司App开发

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

手机网站建设