前言

前面的文章里,我们学习了有关锁的使用,锁的机制是保证同一时刻只能有一个线程访问临界区的资源,也就是通过控制资源的手段来保证线程安全,这固然是一种有效的手段,但程序的运行效率也因此大大降低。那么,有没有更好的方式呢?答案是有的,既然锁是严格控制资源的方式来保证线程安全,那我们可以反其道而行之,增加更多资源,保证每个线程都能得到所需对象,各自为营,互不影响,从而达到线程安全的目的,而ThreadLocal便是采用这样的思路。

ThreadLocal实例

ThreadLocal翻译成中文的话大概可以说是:线程局部变量,也就是只有当前线程能够访问。它的设计作用是为每一个使用该变量的线程都提供一个变量值的副本,每个线程都是改变自己的副本并且不会和其他线程的副本冲突,这样一来,从线程的角度来看,就好像每个线程都拥有了该变量。

下面是一个简单的实例:

public class ThreadLocalDemo {

    static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
}; public static class MyRunnable implements Runnable{ @Override
public void run() {
for (int i = 0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int value = local.get();
System.out.println(Thread.currentThread().getName() + ":" + value);
local.set(value + 1);
}
}
} public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}

上面的代码不难理解,首先是定义了一个名为 local 的ThreadLocal变量,并初识变量的值为0,然后是定义了一个实现Runnable接口的内部类,在其run方法中对local 的值做读取和加1的操作,最后是main方法中开启两个线程来运行内部类实例。

以上就是代码的大概逻辑,运行main函数后,程序的输出结果如下:

Thread-0:0
Thread-1:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2

从结果可以看出,虽然两个线程都共用一个Runnable实例,但两个线程中所展示的ThreadLocal的数据值并不会相互影响,也就是说这种情况下的local 变量保存的数据相当于是线程安全的,只能被当前线程访问。

ThreadLocal实现原理

那么ThreadLocal内部是怎么保证对象是线程私有的呢?毫无疑问,答案需要从源码中查找。回顾前面的代码,可以发现其中调用了ThreadLocal的两个方法setget,我们就从这两个方法入手。

先看 set() 的源码:

public void set(T value) {
Thread t = Thread.currentThread();
// 获取线程的ThreadLocalMap,返回map
ThreadLocalMap map = getMap(t);
if (map != null)
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);
}

set的代码逻辑比较简单,主要是把值设置到当前线程的一个ThreadLocalMap对象中,而ThreadLocalMap可以理解成一个Map,它是定义在Thread类中内部的成员,初始化是为null,

ThreadLocal.ThreadLocalMap threadLocals = null;

不过,与常见的Map实现类,如HashMap之类的不同的是,ThreadLocalMap中的Entry是继承于WeakReference类的,保持了对 “键” 的弱引用和对 “值” 的强引用,这是类的源码:

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
} //省略剩下的源码
....................
}

从源码中中可以看出,Entry构造函数中的参数 k 就是ThreadLocal实例,调用super(k) 表明对 k 是弱引用,使用弱引用的原因在于,当没有强引用指向 ThreadLocal 实例时,它可被回收,从而避免内存泄露,那么为何需要防止内存泄露呢?原因下面会说到。

接着说set方法的逻辑,当调用set方法时,其实是将数据写入threadLocals这个Map对象中,这个Map的key为ThreadLocal当前对象,value就是我们存入的值。而threadLocals本身能保存多个ThreadLocal对象,相当于一个ThreadLocal集合。

接着看 get() 的源码:

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;
}
}
//设置初识值到ThreadLocal中并返回
return setInitialValue();
}
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;
}

get方法的逻辑也是比较简单的,就是直接获取当前线程的ThreadLocalMap对象,如果该对象不为空就返回它的value值,否则就把初始值设置到ThreadLocal中并返回。

看到这,我们大概就能明白为什么ThreadLocal能实现线程私有的原理了,其实就是每个线程都维护着一个ThreadLocal的容器,这个容器就是ThreadLocalMap,可以保存多个ThreadLocal对象。而调用ThreadLocal的set或get方法其实就是对当前线程的ThreadLocal变量操作,与其他线程是分开的,所以才能保证线程私有,也就不存在线程安全的问题了。

然而,该方案虽然能保证线程私有,但却会占用大量的内存,因为每个线程都维护着一个Map,当访问某个ThreadLocal变量后,线程会在自己的Map内维护该ThreadLocal变量与具体实现的映射,如果这些映射一直存在,就表明ThreadLocal 存在引用的情况,那么系统GC就无法回收这些变量,可能会造成内存泄露。

针对这种情况,上面所说的ThreadLocalMap中Entry的弱引用就起作用了。

TheadLocal与同步机制的区别

最后,总结一下ThreadLocal和同步机制之间的区别吧。

实现机制:

同步机制采用了“以时间换空间”的方式,控制资源保证同一时刻只能有一个线程访问。

ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响,但因为每个线程都维护着一份副本,对内存空间的占用会增加。

数据共享:

同步机制是对公共资源做控制访问的方式来保证线程安全,但资源仍是共享状态,可用于线程间的通信;

ThreadLocal是每个线程都有自己的资源(变量)副本,互相之间不影响,也就不存在共享的说法了。

Java并发编程:ThreadLocal的使用以及实现原理解析的更多相关文章

  1. 【Java并发编程】24、Synchronized实现原理解析

    一.概述 我们知道在JDK1.5之前synchronized是一个重量级锁,相对于j.u.c.Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 不过,随着后续Java版本更新对 ...

  2. Java 并发编程:volatile的使用及其原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  3. Java 并发编程——Executor框架和线程池原理

    Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...

  4. Java并发编程-阻塞队列(BlockingQueue)的实现原理

    背景:总结JUC下面的阻塞队列的实现,很方便写生产者消费者模式. 常用操作方法 常用的实现类 ArrayBlockingQueue DelayQueue LinkedBlockingQueue Pri ...

  5. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  6. Java并发编程之深入理解线程池原理及实现

    Java线程池在实际的应用开发中十分广泛.虽然Java1.5之后在JUC包中提供了内置线程池可以拿来就用,但是这之前仍有许多老的应用和系统是需要程序员自己开发的.因此,基于线程池的需求背景.技术要求了 ...

  7. 【转】Java 并发编程:volatile的使用及其原理

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  8. Java 并发编程:volatile的使用及其原理(二)

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  9. JAVA并发编程:相关概念及VOLATILE关键字解析

    一.内存模型的相关概念 由于计算机在执行程序时都是在CPU中运行,临时数据存在主存即物理内存,数据的读取和写入都要和内存交互,CPU的运行速度远远快于内存,会大大降低程序执行的速度,于是就有了高速缓存 ...

  10. 浅谈Java并发编程系列(八)—— LockSupport原理剖析

    LockSupport 用法简介 LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现. LockSupport是用来创建锁和其他同步 ...

随机推荐

  1. ota升级动画修改

    在网上可以搜到很多相关的文章,但是很多文章都是复制粘贴而来的,为了方便后面工作学习,本文会把其中最关键的几个步骤列出来. 首先根据ota升级界面的文字可以确认相关的图片资源的目录在哪里,可以网上搜一下 ...

  2. 软工网络15Alpha阶段敏捷冲刺博客集合

    博客链接集合 第一篇:http://www.cnblogs.com/net15/p/8885400.html 第二篇:http://www.cnblogs.com/net15/p/8893808.ht ...

  3. Do More With These Great Plugins for Windows Live Writer(old)

    This article is out of day,now we use open live wirter, but we don’t have so much works great plugin ...

  4. Vuejs——(13)组件——杂项

    版权声明:出处http://blog.csdn.net/qq20004604   目录(?)[+]   本篇资料来于官方文档: http://cn.vuejs.org/guide/components ...

  5. 11g统计信息自动收集任务

    1.查看统计信息自动收集任务的开启情况. select client_name,status from dba_autotask_client; 2.查看自动收集任务各个窗口的开启情况. col op ...

  6. MySQL DDL--ghost工具学习

    GHOST工作流程图: GHOST工作原理: .首先新建一张ghost表,结构与源表相同 .使用alter命令修改ghost表 3.1.模拟从库命令获取主库上该表的binlog(基于全镜像的行模式的b ...

  7. 准备情人节礼物比写代码难?来看看IT直男给女友们的礼物

    今天是情人节,据说IT直男在每个这样弥漫着恋爱气息的日子里都能把礼物送成"辣眼睛"现场,为了反(zheng)驳(shi)这个观点,小编特意走访了网易云的架构师.工程师.产品经理.程 ...

  8. 从零开始的程序逆向之路 第一章——认识OD(Ollydbg)以及常用汇编扫盲

    作者:Crazyman_Army 原文来自:https://bbs.ichunqiu.com/thread-43041-1-1.html 0×00 序言: 1.自从上次笔者调戏完盗取文件密码大黑客后, ...

  9. phpMyAdmin 4.7.x CSRF 漏洞利用

    作者:Ambulong phpMyAdmin是个知名MySQL/MariaDB在线管理工具,phpMyAdmin团队在4.7.7版本中修复了一个危害严重的CSRF漏洞(PMASA-2017-9),攻击 ...

  10. 使用广播-BroadcastReceiver最详细解析

    女孩:BroadcastReceiver是什么呀? 男孩:Broadcast是广播的意思,在Android中应用程序之间的传输信息的机制,BroadcastReceiver是接收广播通知的组件,广播和 ...