深入解析ThreadLocal
Posted zhixuChen200
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入解析ThreadLocal相关的知识,希望对你有一定的参考价值。
文章目录
前言
多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。
同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal就可以做这件事情,虽然ThreadLocal并不是为了解决这个问题而出现的。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ThreadLocal是什么?
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存
二、ThreadLocal使用示例
代码如下(示例):
public class ThreadLocalTest
//(1)print函数
static void print(String str)
//1.1 打印当前线程本地内存中localVariable变量的值
System.out.println(str + ":" + localVariable.get());
//1.2 清除当前线程本地内存中的localVariable变量
localVariable.remove();
//(2) 创建ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<String>();
public static void main(String[] args)
//(3) 创建线程one
Thread threadOne = new Thread(new Runnable()
public void run()
//3.1 设置线程One中本地变量localVariable的值
localVariable.set("threadOne local variable");
//3.2 调用打印函数
print("threadOne");
//3.3 打印本地变量值
System.out.println("threadOne remove after" + ":" + localVariable.get());
);
//(4) 创建线程two
Thread threadTwo = new Thread(new Runnable()
public void run()
//4.1 设置线程Two中本地变量localVariable的值
localVariable.set("threadTwo local variable");
//4.2 调用打印函数
print("threadTwo");
//4.3 打印本地变量值
System.out.println("threadTwo remove after" + ":" + localVariable.get());
);
//(5)启动线程
threadOne.start();
threadTwo.start();
三、解析ThreadLocal源码
1. void set(T value)
public void set(T value)
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)将当前线程作为key,去查找对应线程中的threadLocals变量,并设置值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//(3)第一次调用就创建当前线程的HashMap
createMap(t, value);
set方法的流程已经很清晰了,我们要看看getMap(t)方法和createMap(t, value)做了什么可以使得每个线程有自己的本地变量。
ThreadLocalMap getMap(Thread t)
return t.threadLocals;
void createMap(Thread t, T firstValue)
t.threadLocals = new ThreadLocalMap(this, firstValue);
class Thread implements Runnable
**
**
** snip
/* 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;
- 从Thread类的源码中可以看到每个线程中都维护的一个 threadLocals 变量,初始的时候是null。
- 第一次调用就会去执行 createMap() 方法,方法中对 threadLocals 进行初始化,并且赋值
- getMap()方法,只是获取点前线程的 threadLocals 变量而已,其实看到这里就已经能够明白ThreadLocal为什么能够保证共享变量的安全性,其实就是一个套壳的工具类,去操作各个线程中的 threadLocals 变量。
2. T get()
public T get()
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals不为null,则返回对应本地变量的值
if (map != null)
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
//(4)如果threadLocals为null,则初始当前线程的threadLocals成员变量
return setInitialValue();
通过上文对set()方法的讲解,这个代码一眼就能清楚流程,我们主要看看setInitialValue()方法做了什么。
private T setInitialValue()
//(5)初始化为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
protected T initialValue()
return null;
如果当前的线程 threadLocals 变量不为空,就设置线程当前的本地变量为null,否者去直接初始化threadLocals 变量,最后返回空。
3. void remove()
public void remove()
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
如以上代码所示,如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。
四、ThreadLocal不支持继承性
1. 例子
public class TestThreadLocal
//(1)创建线程变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args)
//(2) 设置线程变量
threadLocal.set("hello world");
//(3) 启动子线程
Thread thread = new Thread(new Runnable()
public void run()
//(4) 子线程输出线程变量的值
System.out.println("thread:" + threadLocal.get());
);
thread.start();
//(5) 主线程输出线程变量的值
System.out.println("main:" + threadLocal.get());
输出结果:
main:hello world
thread:null
也就是说,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程,自然子线程访问时返回null。那么有没有办法让子线程能访问到父线程中的值?答案是有。
2. InheritableThreadLocal类
为了解决上节提出的问题,InheritableThreadLocal应运而生。InheritableThreadLocal继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。下面看一下InheritableThreadLocal的代码。
public class InheritableThreadLocal<T> extends ThreadLocal<T>
//(1)
protected T childValue(T parentValue)
return parentValue;
//(2)
ThreadLocalMap getMap(Thread t)
return t.inheritableThreadLocals;
//(3)
void createMap(Thread t, T firstValue)
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
由如上代码可知,InheritableThreadLocal继承了ThreadLocal,并重写了三个方法。由代码(3)可知,InheritableThreadLocal重写了createMap方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals。由代码(2)可知,当调用get方法获取当前线程内部的map变量时,获取的是inheritableThreadLocals而不再是threadLocals。
下面我们看一下重写的代码(1)何时执行,以及如何让子线程可以访问父线程的本地变量。这要从创建Thread的代码说起,打开Thread类的默认构造函数,代码如下。
public Thread(Runnable target)
init(null, target, "Thread-" + nextThreadNum(), 0);
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc)
...
//(4)获取当前线程
Thread parent = currentThread();
...
//(5)如果父线程的inheritableThreadLocals变量不为null
if (parent.inheritableThreadLocals ! = null)
//(6)设置子线程中的inheritableThreadLocals变量
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
总结:InheritableThreadLocal类通过重写代码(2)和(3)让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。
以上是关于深入解析ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章