我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么。其实JDK已经为我们提供了ThreadLocal这个东西。


ThreadLocal基本使用

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的主要方法有这么几个:

1
2
3
4
initialValue 初始化
set 赋值
get 取值
remove 清空

下面来看一个简单的使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
public Integer initialValue() {
return 0;
}
};
static class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
threadLocal.set(threadLocal.get() + 1);
}
System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new ThreadDemo()).start();
}
}
}

上方代码使用了10个线程循环对一个threadLocal的值进行一千次的加法,如果我们不知道ThreadLocal的原理的话我们可能会觉得最后打印的值一定是1000、2000、3000。。10000或者是线程不安全的值。
但是如果你执行这段代码你会发现最后打印的都是1000。


ThreadLocal原理剖析

现在我们来看一下ThreadLocal是如何实现为每个线程单独维护一个变量的呢。
先来看一下初始化方法。

1
2
3
protected T initialValue() {
return null;
}

initialValue 默认是返回空的,所以为了避免空指针问题重写了这个方法设置了默认返回值为0,但是呢,虽然这个方法好像是设置默认值的,但是还没有生效,具体请接着往下看。

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

我们可以看到set方法首先会获取当前线程,然后通过一个getMap方法获取了ThreadLocalMap,接着来看一下这个map是怎么来的呢。

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

这个map是在Thread类维护的一个map,下方是Thread类维护的此变量。默认这个map是空的。

1
ThreadLocal.ThreadLocalMap threadLocals = null;

接着往下看代码,如果获取的时候map不为空,则通过set方法把Thread类的threadLocals变量更新。如果是第一次创建的时候则初始化Thread的threadLocals变量。
下方是createMap的代码:

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

接下来看个get方法就比较容易理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

注意关注最后的一个return,看到调用的这个方法名我们就可以发现这个ThreadLocal的初始化原来是当第一调用get方法时如果还没有被set的时候才会去获取initialValue 方法的返回值。

1
2
3
4
5
6
7
8
9
10
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}


使用ThreadLocal最应该注意的事项

首先来看一下线程退出的办法:

1
2
3
4
5
6
7
8
9
10
11
12
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
target = null;
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}

我们看到当线程结束的时候上方第7行会把ThreadLocal的值制为空,这个东西本身是没问题的。但是,如果你是使用的线程池,这个问题可就大了!!!
要知道线程池里的线程执行完一个任务之后紧接着下一个,这中间线程可不会结束,下一个任务获得Thread的值可是上一个任务的遗留数据。
下面是这个问题的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
public Integer initialValue() {
return 0;
}
};
static class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
threadLocal.set(threadLocal.get() + 1);
}
System.out.println("thread :" + Thread.currentThread().getId() + " is" + threadLocal.get());
//threadLocal.remove();
}
}
public static void main(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.submit(new Thread(new ThreadDemo()));
}
}

执行这段代码你就会发现同样的操作在线程池里已经得不到一样的结果了。想要解决这种问题也很简单,只需要把ThreadLocal的值在线程执行完清空就可以了。把第14行注释的代码放开再执行以下你就明白了。


InheritableThreadLocal

其实ThreadLocal还有一个比较强大的子类InheritableThreadLocal,它呢可以把父线程生成的变量传递给子线程。
下面来看一下代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InheritableThreadLocalDemo {
private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<Integer>();
static class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
inheritableThreadLocal.set(inheritableThreadLocal.get() + 1);
}
System.out.println("thread :" + Thread.currentThread().getId() + " is" + inheritableThreadLocal.get());
}
}
public static void main(String[] args) {
inheritableThreadLocal.set(24);
for (int i = 0; i < 10; i++) {
new Thread(new ThreadDemo()).start();
}
}
}

执行代码会发现程序输出全是1024,这就是因为InheritableThreadLocal吧在主线程设置的值24传递到了那10个子线程中。


InheritableThreadLocal原理剖析

接下来我们来看一下InheritableThreadLocal为什么可以实现这种功能呢。
InheritableThreadLocal是ThreadLocal的子类,
与ThreadLocal相同的set方法

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

不同点是InheritableThreadLocal重写了createMap方法,将值赋值给了线程的inheritableThreadLocals变量。

1
2
3
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

再跟进去Thread类的源码看inheritableThreadLocals变量你会发现:我去,这不是跟Threadlocal一样么,同样初始值为null,线程退出的时候清空。没错,就是这样的。也就是说它其实也是一个线程私有的变量,ThreadLocal的功能它是都有的。

那么它又是怎么把父线程的变量传递到子线程的呢?
接着看Thread的构造方法

1
2
3
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}

一路追踪init方法你会看见这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}

仔细观察倒数第5行到倒数第二行你就明白了。

本文所有源码https://github.com/shiyujun/syj-study-demo

ThreadLocal及InheritableThreadLocal的原理剖析的更多相关文章

  1. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)

      上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢?   测试代码 p ...

  2. 66、Spark Streaming:数据处理原理剖析与源码分析(block与batch关系透彻解析)

    一.数据处理原理剖析 每隔我们设置的batch interval 的time,就去找ReceiverTracker,将其中的,从上次划分batch的时间,到目前为止的这个batch interval ...

  3. ThreadLocal的使用及原理解析

    # 基本使用 JDK的lang包下提供了ThreadLocal类,我们可以使用它创建一个线程变量,线程变量的作用域仅在于此线程内.<br />用2个示例来展示一下ThreadLocal的用 ...

  4. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  5. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  6. 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】

    原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...

  7. 【Xamarin 跨平台机制原理剖析】

    原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...

  8. iPhone/Mac Objective-C内存管理教程和原理剖析

    http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...

  9. 【Xamain 跨平台机制原理剖析】

    原文:[Xamain 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原生 ...

随机推荐

  1. 关于PCB走线能不能走锐角的讨论

    (此文参考吴川斌的博客) 很多PCB工程师都知道Layout走线时忌走直角,那么锐角能走吗? 回答当然是否定的!为什么呢? 这里先不说锐角对高速信号走线会不会造成负面影响,单从PCB DFM(可制造性 ...

  2. MVC View中获取action、controller、area名称、参数

    获取控制器名称: ViewContext.RouteData.Values["controller"].ToString(); 获取Action名称: ViewContext.Ro ...

  3. angular2报错

    1.运行ng serve的时候卡在95% emitting LicenseWebpackPlugin 放弃使用cnpm,使用yarn 删除node_modules (不需要删除project目录重新n ...

  4. Spring boot实现原生websocket

    网上的大部分教程是基于sockjs,这篇文章内容则是基于原生协议. 后台Spring boot 配置 @Configuration @EnableWebSocket public class WebS ...

  5. 腾讯工蜂Git关联Jenkins Hooks

    现在国内外Git平台非常多,最近维护的腾讯工蜂免费公网版本git.code.tencent.com,免注册(建议使用微信登录,舒服)即可使用私有仓库.对小型团队体验还不错,如果要关联Jenkins进行 ...

  6. BZOJ2759 一个动态树好题 LCT

    题解: 的确是动态树好题 首先由于每个点只有一个出边 这个图构成了基环内向树 我们观察那个同余方程组 一旦形成环的话我们就能知道环上点以及能连向环上点的值是多少了 所以我们只需要用一种结构来维护两个不 ...

  7. HeadFirst学习笔记-1. 设计模式入门

    1.概念 在开始学习前,我们先了解一些概念,方便我们接下来的学习. OO基础 抽象 继承 多态 封装 OO原则 封装变化 多用组合,少用继承 针对接口编程,不针对实现编程 设计模式 设计模式(Desi ...

  8. keepalived介绍及工作原理

    keepalived介绍keepalived观察其名可知,保持存活,在网络里面就是保持在线了,也就是所谓的高可用或热备,它集群管理中保证集群高可用的一个服务软件,其功能类似于heartbeat,用来防 ...

  9. Jmeter3.2源码编译环境搭建

    1.下载jmeter3.2源码 https://github.com/apache/jmeter/tree/v3_2 https://blog.csdn.net/fly_to_higher/artic ...

  10. 原生js触碰到底部触发函数;

    /** function __morebook(){ console.log(123) } ScrollBottom(function(){__morebook();}); **/ //如果直接写一个 ...