深入分析ThreadLocal
来源:互联网 发布:linux系统是什么 编辑:程序博客网 时间:2024/06/05 19:13
在JDK1.2 版本中,提供了java.lang.ThreadLocal。它为解决多线程并发问题提供了一种新的思路,有其特定的应用场景。
1. 如何理解ThreadLocal
ThreadLocal,很多人叫它线程本地变量。它为每个线程都创建一个副本,每个线程访问自己内部的副本变量。不会影响其他线程的副本变量。变量是同一个,但是每个线程都使用此变量的一个新的副本,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal一般可用于数据库连接的管理,可看下边的例子:
public class ConnectionManage private static Connection connection = null; /** * 获取数据量连接 */ public static Connection openConnection() { if (connection == null) { connection = DriverManager.getConnection(); } return connection; } /** * 关闭数据库连接 */ public static void closeConnection(){ if (connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }}
上边的代码在多线程中使用会出现问题。因为connection是共享变量,这两个方法没有进行同步,可能在openConnection方法中,多次创建connection。另外,可能一个线程在执行数据库操作时,另个一个线程就调用了closeConnection方法,将数据库连接关闭了。所以,如果要保证上边的代码正确运行,必须进行同步。但是,这会大大影响执行效率,因为,一个线程在使用Connection时,其他线程就必须等待。我们可以这样思考,是不是可以不将connection变量设为线程私有变量,不进行共享。这样是可以的,如果直接声明成:
private Connection connection = null;
然后,给每个线程创建私有的变量。这样是可以实现线程安全的,但是,这创建了很多个数据库连接,这会使服务器压力很大,严重影响程序的执行效率。并且,数据库连接一般是设计为单例的。所以,不能设计成这种形式。
这种情况下,就可以使用ThreadLocal。ThreadLocal在每个线程都为共享变量创建了副本,即:会在每个线程的内部都会有一个该变量,且不同线程之间不会影响,从而达到了线程隔离的效果。
1.ThreadLocal的实现
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份共享变量的副本,因此可以同时访问而互不影响。
private static ThreadLocal<Connection> connection = new ThreadLocal<Connection>();
ThreadLocal有4个方法,分别是:
- public void set(T value):将值放入线程局部变量中
- public T get():从线程局部变量中获取值
- public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)(jdk1.5)
protected T initialValue():返回线程局部变量中的初始值(默认为 null)
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) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
从上边的代码可以看到,getMap(t)方法,获得的当前线程的一个ThreadLocalMap。然后获得Entry <key,value>。获取成功,则返回本线程的value。如果,map为空,说明还没有此变量副本。调用setInitialValue()方法。下边,进行分析每一行的具体含义:ThreadLocalMap map = getMap(t);
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
从上边的代码可以看到,返回的是当前线程的一个成员变量threadLocals。这个成员变量在Thread类中:定义为: ThreadLocal.ThreadLocalMap threadLocals = null;这个变量的类型是ThreadLocal类的内部类ThreadLocalMap。然后进入ThreadLocal中,查看此类型实现:
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
ThreadLocalMap的Entry继承了 WeakReference,并且使用当前ThreadLocal作为键值。这里将ThreadLocal作为键值,是因为可能一个线程中有多个ThreadLocal的变量。而每个线程是 ThreadLocal.ThreadLocalMap threadLocals = null;获得线程独有的Entry的。然后,再看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; }
如果获得的Map不为空,就设置键值对,为空,就调用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 */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
设置当前线程的成员变量threadLocals。这基本就是ThreadLocal的实现原理,总结一下就是:在每个线程的Thread内部有一个 ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它存储了实际的变量副本。键值为当前ThreadLocal变量,value为变量副本。初始时候,在Thread中,threadLocals为空,当ThreadLocal变量调用get()或者set()方法时,会对Thread类中的threadLocals进行初始化,键值为当前ThreadLocal变量,value为变量副本。变量副本是存在线程的Thread类中的threadLocals变量中的。
## 注意点##
- 在进行get之前,必须进行set,否则会出现空指针。 如果想在set之前就调用get,则必须重写initialValue方法。
原因是: 如果没有set的话,则map返回为空,则会调用setInitialValue方法,此方法创建副本,并且返回value。value的获得是通过:T value = initialValue();获得的,如果没有重写这个方法,其默认实现是:
protected T initialValue() { return null; }
其返回值是null,从而,没有先set,就调用get时,会返回null,从而报空指针异常。set方法为:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
- ThreadLocal常见的应用场景
一般用ThreadLocal来解决数据库连接和Session管理等。ThreadLocal主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
- 深入分析ThreadLocal
- 关于ThreadLocal深入分析
- 【Java并发编程】深入分析ThreadLocal(八)
- ThreadLocal深入理解与内存泄露分析
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 深入分析 ThreadLocal 内存泄漏问题
- 【死磕Java并发】-----深入分析ThreadLocal
- 深入分析 ThreadLocal 内存泄漏问题
- ThreadLocal深入
- 深入ThreadLocal
- Java 并发编程深入学习——ThreadLocal 原理分析
- ThreadLocal 分析
- 如何用机器学习对文本分类
- 第八届“蓝桥杯”赛后总结
- Problem A: 时间类的拷贝和整体读写
- git命令清单
- SDUT-数据结构实验之二叉树的建立与遍历
- 深入分析ThreadLocal
- WindowsJNDI查询目录下所有的根目录
- 高通CAMIF和OV sensor调试经验分享
- 统计| p值的计算
- AngularJS 1.5 版本Component详解
- 做个寡言,但心有一片海的人
- 南阳理工oj 题目289 苹果 01背包
- 2017年上海金马五校程序设计竞赛(网上资格赛)Problem A : Corn's new language
- 用静态工厂方法代替构造器