JAVA并发编程(六)ThreadLocal

mac2023-02-01  12

JAVA并发编程(六)ThreadLocal

1、简单了解ThreadLocal2、使用ThreadLocal的简单示例3、从源码深入理解ThreadLocal4、ThreadLocal不支持继承性

1、简单了解ThreadLocal

      ThreadLocal是JDK包提供的一个类,它提供了线程本地变量,也就是说如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有一个这个变量的副本。当多个线程操作这个变量的时候,其实是操作自己的副本变量,也就是每个线程所拥有的本地变量,这样就避免了线程安全访问的问题。当创建了一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。       如果你还不是太理解,我这里来打个比方。线程就相当于一个人一样,而线程要做的事相当于一个任务,任务来了,人来处理,处理完毕之后,再处理下一个任务。人身上是不是有很多口袋,人刚开始准备处理任务的时候,我们把任务的编号放在处理者的口袋中,然后处理者一路携带着,处理过程中如果需要用到这个编号,直接从口袋中获取就可以了。那么刚好java中线程设计的时候也考虑到了这些问题,Thread对象中就有很多口袋,用来放东西。Thread类中有这么一个变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

      这个就是用来操作Thread中所有口袋的东西,ThreadLocalMap源码中有一个数组(有兴趣的可以去看一下源码),对应处理者身上很多口袋一样,数组中的每个元素对应一个口袋。

      如何来操作Thread中的这些口袋呢,java为我们提供了一个类ThreadLocal,ThreadLocal对象用来操作Thread中的某一个口袋,可以向这个口袋中放东西、获取里面的东西、清除里面的东西,这个口袋一次性只能放一个东西,重复放东西会将里面已经存在的东西覆盖掉。

2、使用ThreadLocal的简单示例

      话不多说,直接上代码,看代码才能知道怎么用。

public class ThreadLocalTest { //1.创建ThreadLocal变量 为Sring类型 public static ThreadLocal<String> localVariable = new ThreadLocal<>(); //2.创建一个打印ThreadLocal的方法 public static void printThreadLcoal(String str){ //打印当前线程的ThreadLocal变量的值 System.out.println(str+":"+localVariable.get()); //清除ThreadLocal的值 localVariable.remove(); } public static void main(String[] args) { //1.创建线程One Thread threadOne = new Thread(new Runnable() { @Override public void run() { //设置One线程的ThreadLocal值 localVariable.set("ThreadOne local variable"); //将One线程的ThreadLocal值打印出来 printThreadLcoal("threadOne"); //打印清除之后的ThreadLocal的值 System.out.println("ThreadOne remove after"+":"+localVariable.get()); } }); //1.创建线程Two Thread threadTwo = new Thread(new Runnable() { @Override public void run() { //设置Two线程的ThreadLocal值 localVariable.set("ThreadTwo local variable"); //将Two线程的ThreadLocal值打印出来 printThreadLcoal("threadTwo"); //打印清除之后的ThreadLocal的值 System.out.println("ThreadTwo remove after"+":"+localVariable.get()); } }); //启动线程 threadOne.start(); threadTwo.start(); } }

      代码里面都有注释,相信大家都能看懂。其中无非就是set和get还有remove几个方法。

3、从源码深入理解ThreadLocal

      从上面我们可以知道,ThreadLocal是用来操作口袋的,也就是往口袋里面装东西,拿东西的。而口袋又是属于线程的。所以让我们先来看看Thread的源码中有关口袋的定义:

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

      翻译一下注释里面的内容:第一段是与此线程相关的threadlocal值。此映射由threadlocal类维护。第二段是与此线程相关的可继承的threadlocal值。此映射由threadlocal类维护。

      单看翻译好像不太能理解哦。我这里解释一下,其实这就是在Thread类里面定义了两个变量,是Map类型的,也就是键值对形式存在的,也就是我们所说的口袋。每个线程都会有的。我们ThreadLocal操作的正是这两个成员变量。只有当线程第一次调用ThreadLocal的set或者get方法时才会创建他们,也就是实例化。

      接下来我们来看看ThreadLocal是怎样往口袋里面装东西的。下面是源码:代码的解释也在下面

public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的Map,也就是拿到线程的口袋 ThreadLocalMap map = getMap(t); //判断口袋里面是否为空 if (map != null) //不是空的,就把当前ThreadLocal对象作为key,value值自己给定 map.set(this, value); else //是空的就创建一个Map createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

      下面看看他怎么获得口袋里面的东西吧

public T get() { //获得当前线程 Thread t = Thread.currentThread(); //获得当前线程的口袋 ThreadLocalMap map = getMap(t); //判断Map是否为空 if (map != null) { //通过当前ThreadLocal对象找到对应的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } //设置初始值,并返回初始值,这里的初始值为null private T setInitialValue() { //将null赋值给value,initialValue()返回的就是null T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //获得口袋 ThreadLocalMap map = getMap(t); //日常判断一下口袋是否为空 if (map != null) map.set(this, value); else createMap(t, value); //返回null return value; }

4、ThreadLocal不支持继承性

先来看一段代码吧

public class ThreadLocalExtendTest { //1.创建ThreadLocal变量 为Sring类型 public static ThreadLocal<String> localVariable = new ThreadLocal<>(); public static void main(String[] args) { //在父线程的口袋里面加入HelloWorld localVariable.set("Hello World"); //创建并启动子线程 new Thread(new Runnable() { @Override public void run() { //输出子线程口袋里面的值 System.out.println("child:"+localVariable.get()); } }).start(); //输出父线程口袋里面的值 System.out.println("main:"+localVariable.get()); } }

输出结果:

main:Hello World child:null

      从上面的代码来看,ThreadLocal在父线程中被设置后,在子线程里面是获取不到,因为这是两个不同的线程,只有父线程口袋里装了东西,子线程却并没有继承父线程的口袋,所以ThreadLocal并没有继承性。

最新回复(0)