博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅谈java.lang.ThreadLocal类
阅读量:7071 次
发布时间:2019-06-28

本文共 6801 字,大约阅读时间需要 22 分钟。

hot3.png

相信读者在网上也看了很多关于ThreadLocal的资料,很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路;ThreadLocal的目的是为了解决多线程访问资源时的共享问题。如果你也这样认为的,那现在给你10秒钟,清空之前对ThreadLocal的错误的认知!

看看JDK中的源码是怎么写的:

This class provides thread-local variables.  These variables differ fromtheir normal counterparts in that each thread that accesses one (via itsget or set method) has its own, independently initializedcopy of the variable.  ThreadLocal instances are typically privatestatic fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

翻译过来大概是这样的(英文不好,如有更好的翻译,请留言说明):

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。

可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。

有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程

1、ThreadLocal基本操作

  • 构造函数

ThreadLocal的构造函数是这样的:  

/*** Creates a thread local variable.*/public ThreadLocal() {}

内部啥也没做。

  • initialValue函数

initialValue函数用来设置ThreadLocal的初始值,如下:

protected T initialValue() {    return null;}

该函数在调用get函数的时候会第一次调用,但是如果一开始就调用了set函数,则该函数不会被调用。通常该函数只会被调用一次,除非手动调用了remove函数之后又调用get函数,这种情况下,get函数中还是会调用initialValue函数。该函数是protected类型的,很显然是建议在子类重载该函数的,所以通常该函数都会以匿名内部类的形式被重载,以指定初始值,比如:

package test;public class ThreadLocalDemo {         private final static ThreadLocal
seqNum = new ThreadLocal
() { @Override protected Integer initialValue() { return 0; } }; }
  • get函数

该函数用来获取与当前线程关联的ThreadLocal的值,如下:

public T get()

如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回。

  • set函数

set函数用来设置当前线程的该ThreadLocal的值,如下:

public void set(T value)

设置当前线程的ThreadLocal的值为value。

  • remove函数

remove函数用来将当前线程的ThreadLocal绑定的值删除,如下:

public void remove()

在某些情况下需要手动调用该函数,防止内存泄露。

2、代码演示

学习了最基本的操作之后,我们用一段代码来演示ThreadLocal的用法,该例子实现下面这个场景:

有5个线程,这5个线程都有一个序列值seqNum,初始值为0,线程运行时序列值seqNum递增。

代码实现:

package test;public class ThreadLocalDemo implements Runnable {        public static void main(String[] args) {        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();                for (int i = 0; i < 5; i ++) {            Thread t = new Thread(threadLocalDemo, String.valueOf(i));            t.start();        }    }        private final static ThreadLocal
seqNum = new ThreadLocal
() { @Override protected Integer initialValue() { return 0; } }; public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } @Override public void run() { for (int i = 0; i < 3; i ++) { System.out.println("thread[" + Thread.currentThread().getName() + "] --> [" + getNextNum() + "]"); } } }

执行结果为:

thread[0] --> [1]thread[0] --> [2]thread[0] --> [3]thread[4] --> [1]thread[1] --> [1]thread[1] --> [2]thread[1] --> [3]thread[2] --> [1]thread[3] --> [1]thread[3] --> [2]thread[2] --> [2]thread[4] --> [2]thread[4] --> [3]thread[2] --> [3]thread[3] --> [3]

可以看到,各个线程的seqNum值是相互独立的,本线程的递增操作不会影响到其他线程的值,真正达到了线程内部隔离的效果。

3、如何实现

看了基本介绍,也看了最简单的效果演示之后,我们更应该好好研究下ThreadLocal内部的实现原理。如果给你设计,你会怎么设计?相信大部分人会有这样的想法:

每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。

没错,这是最简单的设计方案,JDK最早期的ThreadLocal就是这样设计的。JDK1.3(不确定是否是1.3)之后ThreadLocal的设计换了一种方式。

我们先看看JDK7的ThreadLocal的get方法的源码:

/**     * Returns the value in the current thread's copy of this     * thread-local variable.  If the variable has no value for the     * current thread, it is first initialized to the value returned     * by an invocation of the {@link #initialValue} method.     *     * @return the current thread's value of this thread-local     */    public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }

其中getMap的源码:

/**     * Get the map associated with a ThreadLocal. Overridden in     * InheritableThreadLocal.     *     * @param  t the current thread     * @return the map     */    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }

setInitialValue函数的源码:

/**     * Variant of set() to establish initialValue. Used instead     * of set() in case user has overridden the set() method.     *     * @return the initial value     */    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;    }

createMap函数的源码:

/**     * Create the map associated with a ThreadLocal. Overridden in     * InheritableThreadLocal.     *     * @param t the current thread     * @param firstValue value for the initial entry of the map     * @param map the map to store.     */    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }

简单解析一下,get方法的流程是这样的:

  1. 首先获取当前线程;
  2. 根据当前线程获取一个Map;
  3. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的value e,否则转到5;
  4. 如果e不为null,则返回e.value,否则转到5;
  5. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map。

然后需要注意的是Thread类中包含一个成员变量:

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

所以,可以总结一下ThreadLocal的设计思路: 

每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。 
这个方案刚好与我们开始说的简单的设计方案相反。查阅了一下资料,这样设计的主要有以下几点优势:

  • 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)
  • 当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。

4、使用场景

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。

1. synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。

2. ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离

当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。

参考

http://blog.csdn.net/winwill2012/article/details/71625570

http://blog.csdn.net/lufeng20/article/details/24314381

转载于:https://my.oschina.net/lienson/blog/1550295

你可能感兴趣的文章
10个财务工作中常用的 Excel 万能公式
查看>>
苹果iOS手机后门的”诊断功能论“不攻自破
查看>>
记一次使用utl_http方法调用接口,报字符或值错误
查看>>
APDU命令与响应格式【转】
查看>>
CodeForces484A Bits(贪心)
查看>>
前端生成图表
查看>>
数据结构:二叉树(前,中,后,层次)非递归遍历。
查看>>
nodejs-ORM 操作数据库中间件waterline的使用
查看>>
不允许使用抽象类类型
查看>>
Android利用方向传感器获得手机的相对角度实例说明
查看>>
从头认识java-13.9 隐式和显示的创建类型实例
查看>>
git笔记之安装使用
查看>>
jquery中的动画
查看>>
用python将MSCOCO和Caltech行人检测数据集转化成VOC格式
查看>>
Java过滤器处理Ajax请求,Java拦截器处理Ajax请求,java 判断请求是不是ajax请求
查看>>
centos/linux下的安装Tomcat
查看>>
ieda 运行web--导入其它jar包
查看>>
[ios]关于ios开发图片尺寸的建议
查看>>
C# IEnumerator的使用
查看>>
Windows获取远程Linux局域网内的mysql连接
查看>>