在类中定义一个ThreadLocal成员变量怎么用?

2020-12-02 14:51发布

2条回答
无需指教
2楼 · 2020-12-03 08:41

一.成员变量和局部变量

1.程序例子:

public class HelloThreadTest{public static void main(String[] args){HelloThread r = new HelloThread();Thread t1 = new Thread(r);Thread t2 = new Thread(r);t1.start();t2.start();}}class HelloThread implements Runnable{int i;@Overridepublic void run(){while (true){System.out.println("Hello number: " + i++);try{Thread.sleep((long) Math.random() * 1000);}catch (InterruptedException e){e.printStackTrace();}if (50 == i){break;}}}}

        该例子中,HelloThread类实现了Runnable接口,其中run()方法的主要工作是输出"Hello number: "字符串加数字i,并且同时递增i,当i到达50时,退出循环。

  main()方法中生成了一个HelloThread类的对象r,并且利用这个一个对象生成了两个线程。

  程序的执行结果是:顺次打印了0到49的数字,共50个数字。

  这是因为,i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因为由r构造,所以共享了同一个i

  当我们改变代码如下时(原先的成员变量i被注释掉,增加了方法中的局部变量i):

public class HelloThreadTest{public static void main(String[] args){HelloThread r = new HelloThread();Thread t1 = new Thread(r);Thread t2 = new Thread(r);t1.start();t2.start();}}class HelloThread implements Runnable{// int i;// 若i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因为由r构造,所以共享了同一个i// 打印结果是0到49的数字@Overridepublic void run(){int i = 0;// 每一个线程都会拥有自己的一份局部变量的拷贝// 线程之间互不影响// 所以会打印100个数字,0到49每个数字都是两遍while (true){System.out.println("Hello number: " + i++);try{Thread.sleep((long) Math.random() * 1000);}catch (InterruptedException e){e.printStackTrace();}if (50 == i){break;}}}}

如注释中所述,由于局部变量对于每一个线程来说都有自己的拷贝,所以各个线程之间不再共享同一个变量,输出结果为100个数字,实际上是两组,每组都是0到49的50个数字,并且两组数字之间随意地穿插在一起。 

2.小结

  如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。

  如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。

局部变量定义:在方法内定义的变量称为“局部变量”或“临时变量”,方法结束后局部变量占用的内存将被释放。

成员变量定义:在类体的变量部分中定义的变量,也称为字段。

全局变量定义:java中没有全局变量的定义,全局变量,又称“外部变量”,它不是属于哪个方法,作用域从定义的地址开始到源文件结束。

注意事项:当局部变量与全局变量重名时,起作用的是局部变量。

二.ThreadLocal

1.ThreadLocal概述

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。
  线程局部变量并不是Java的新发明,很多语言(如IBM XL、FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,通过ThreadLocal的类提供支持。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,这也是为什么线程局部变量没有在Java开发者中得到很好普及的原因。

  学习JDK中的类,首先看下JDK API对此类的描述,描述如下:

  该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
  API表达了下面几种观点:

  1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。

  2、ThreadLocal在类中通常定义为静态变量。

  3、每个线程有自己的一个ThreadLocal,它是变量的一个“拷贝”,修改它不影响其他线程。

  既然定义为类变量,为何为每个线程维护一个副本(姑且称为“拷贝”容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。

  ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。

2. ThreadLocal方法及使用示例

  ThreadLocal类在Spring,Hibernate等框架中起到了很大的作用。为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类,包括内部类ThreadLocalMap,和线程类Thread。所有方法如下图:

四个核心方法说明如下:

                   T         get()                   返回此线程局部变量的当前线程副本中的值。

 protected  T         initialValue()       返回此线程局部变量的当前线程的“初始值”。

                   void remove()             移除此线程局部变量当前线程的值。

                   void set(T value)         将此线程局部变量的当前线程副本中的值设置为指定值。

 

三.ThreadLocal与局部变量

1.程序列子

public class ThreadLocalLearn {static ThreadLocal tl = new ThreadLocal(){protected IntHolder initialValue() {return new IntHolder();}};public static void main(String args[]) {for(int i=0; i<5 i++) {Thread th = new Thread(new ThreadTest(tl, i));th.start();}}}class ThreadTest implements Runnable{ThreadLocal> tl ; //threadlocal变量 int i;  //线程局部变量 (ThreadTest的成员变量)int a = 3; //线程局部变量(ThreadTest的成员变量) public ThreadTest(ThreadLocal tl, int i) {super();this.tl = tl;this.i = i;}@Override public void run() {tl.get().increAndGet();a++;System.out.println(tl.get().getA() + " ");System.out.println("a : " + a);}}class IntHolder{int a = 1;public int getA() {return a;}public void setA(int a) {this.a = a;}public int increAndGet() {return ++a;}}

代码的运行结果如下:

2 a : 42 a : 42 a : 42 a : 42 a : 4

          可以看到,局部变量和ThreadLocal起到的作用是一样的,保证了并发环境下数据的安全性。

2.小结

根据上述结论那就是说,完全可以用局部变量来代替ThreadLocal咯,这样想法对么?我们看一看官方对于ThreadLocal的描述:

    This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻译起来就是

    ThreadLocal提供的是一种线程局部变量。这些变量不同于其它变量的点在于每个线程在获取变量的时候,都拥有它自己相对独立的变量初始化拷贝。ThreadL:ocal的实例一般是私有静态的,可以做到与一个线程绑定某一种状态。PS:有更好的翻译请指教。

      所以就这段话而言,我们知道ThreadLocal不是为了满足多线程安全而开发出来的,因为局部变量已经足够安全。ThreadLocal是为了方便线程处理自己的某种状态。
      可以看到ThreadLocal实例化所处的位置,是一个线程共有区域。好比一个银行和个人,我们可以把钱存在银行,也可以把钱存在家。存在家里的钱是局部变量,仅供个人使用;存在银行里的钱也不是说可以让别人随便使用,只有我们以个人身份去获取才能得到。所以说ThreadLocal封装的变量我们是在外面某个区域保存了处于我们个人的一个状态,只允许我们自己去访问和修改的状态。
      ThreadLocal同时提供了初始化的机制,在实例化时重写initialValue()方法,便可实现变量的初始化工作

但注意安全性问题

//method 1 static ThreadLocal tl = new ThreadLocal(){protected IntHolder initialValue() {return new IntHolder();}};//method 2 IntHolder intHolder = new IntHolder();static ThreadLocal tl = new ThreadLocal(){protected IntHolder initialValue() {return intHolder;}};

         方法一和方法二都可以实现初始化工作,但是方法二不能保证线程变量的安全性,因为引用拷贝指向的是同一个实例,对引用拷贝的修改,等同于对实例的修改。

当然,也可以在判断ThreadlLocal获取数据为空时,在线程内部为ThreadLocal实例化一个数据。如下

if(null == tl.get()) {tl.set(new IntHolder());}

 


爱学习的小巴
3楼 · 2020-12-03 19:06

ThreadLocal是线程局部变量,所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量。在各个Java web的各种框架中ThreadLocal几乎已经被用烂了,spring中有使用,mybatis中也有使用,hibernate中也有使用,甚至我们写个分页也用ThreadLocal来传递参数......这也从侧面说明了ThreadLocal十分的给力。ThreadLocal有两个简单的方法,一个set()。方法,一个get()方法:简单代码应用ThreadLocalTest类有两个方法,一个是start方法,一个是end方法,start记录开始时间,end方法记录结束时间,这个方法可以简单的用在统计耗时的功能上,在方法的入口前执行start,在方法被调用之后调用end方法,好处是两个方法的调用不用再一个方法或者类中,比如在aop(面向切面编程)中,在方法调用前的切入点执行start方法,在方法调用之后调用end方法,这样依旧可以得到方法执行的耗时。就是在Thread中有一个类似于Map的东西,并且默认为空,在我们声明了ThreadLocal的时候,ThreadLocal把这个Map初始化了,并且把ThreadLocal和value绑定到了这个map中,也就是说这个map的key值是ThreadLocal,但是我在刚开始想的是将Thread当做key这样做到底有什么好处呢?优点1:这样做把value放在了线程当中,这样线程死亡,value随之回收,而原来的方式没有放在线程中,Thread死亡的时候value不确定是不是回收,所以这个改进更加人性化了。以为value定义的初衷就是属于Thread的优点2:性能提升了,想一想,如果按照原来的方法,把整个Thread放入到map中的话,如果放入的线程很多的话,这个map肯定会很大的,说白了就是Thread占用的空间>ThreadLocal占用的空间,空间大了,map在get的时候性能肯定是会下降的。


相关问题推荐

  • 大数据怎么样?2021-01-13 14:45
    回答 36

    大数据薪资待遇挺可观的

  • 回答 7

    用的挺多的呀,首先以前很多项目都是基于ssm的,所以现在如果不升级的话,就需要维护,然后现在很多项目也不一定非得用比如springboot或者springcloud来处理。

  • 回答 3
    已采纳

    大数据的主要特点有哪些?大数据这个概念自被各界媒体所关注后,便一直站在风口浪尖引人注目。不落后的你想要深入浅出地了解大数据?大圣众包以4V为你详尽讲述大数据的四个特点。大数据主要特点:1.准确(Veracity)这是一个在讨论大数据时时常被忽略的一个属性...

  • 回答 8

    Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句

  • 回答 9

    redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符串数组,以下简称C字符串)。C字符串并不能满足redis对字符串安全性、效率以及功能的要求,所以Ridis自定义SDS抽象类型。       Redis中,C字符串只会作为字符串字面量(string literal)用...

  • 大数据框架有哪些?2020-05-07 20:16
    回答 6

    Impala:hadoop的sql平台、支持hbase/hdfs、支持超大数据、支持多并发、sql支持好、对内存依赖比较严重。需要自己优化,并且有的语句超过内存会报错。Spark:各种格式、各种计算(机器学习、图形计算)、可sql、可代码处理、支持scala/java/python语言开发。...

  • 回答 7

    世界编程语言有很多种,但在网络编程中应用比较广泛又适合大数据开发的java是比较合适的,因为java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。 如果你对java有一定的了解,就更应该清楚java是一个强类型编程...

  • 回答 5

    1、模板热部署        在SpringBoot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭模版引擎的缓存,如下:        Thymeleaf的配置:spring.thymeleaf...

  • 回答 2

    mysql默认用户名是 root ,默认没有密码

  • 回答 2

        方法一:使用ThreadLocal,ThreadLocal会为每一个线程提供一个独立的变量副本,这样在多线程对数据访问就不会出现冲突。因为每一个线程都拥有自己的变量副本,因此也就不需要同步该变量。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可...

  • 回答 4

    我们知道Spring相对于之前框架的明显一点区别就是Spring容器生成的Bean都是默认单例的,初读到这里的时候我也是有点疑惑,所以写这篇文章来谈谈线程安全和单例多例的问题。在讲单例和线程安全之前首先我们要明白一点,那就是在单例模式下多线程也可以同时访问...

  • 回答 2

    一、在 SQL 映射文件的 select 标签中添加 useGeneratedKeys=true 属性与 keyProperty=  属性,keyProperty 的值表示的是将获取到的自增主键值赋给 JavaBean 中的某个字段。                  insert into t_employee(username, ge...

没有解决我的问题,去提问