另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html

本文参考:
http://blog.csdn.net/lufeng20/article/details/24314381
http://www.cnblogs.com/chenying99/articles/3405161.html

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

void set(Object value) 设置当前线程的线程局部变量的值。 
public Object get() 该方法返回当前线程所对应的线程局部变量。 
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。
注意,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。
这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

一、知其然

synchronized这类线程同步的机制可以解决多线程并发问题,在这种解决方案下,多个线程访问到的,都是同一份变量的内容。为了防止在多线程访问的过程中,可能会出现的并发错误。不得不对多个线程的访问进行同步,这样也就意味着,多个线程必须先后对变量的值进行访问或者修改,这是一种以延长访问时间来换取线程安全性的策略。

而ThreadLocal类为每一个线程都维护了自己独有的变量拷贝。每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,那就没有任何必要对这些线程进行同步,它们也能最大限度的由CPU调度,并发执行。并且由于每个线程在访问该变量时,读取和修改的,都是自己独有的那一份变量拷贝,变量被彻底封闭在每个访问的线程中,并发错误出现的可能也完全消除了。对比前一种方案,这是一种以空间来换取线程安全性的策略。

来看一个运用ThreadLocal来实现数据库连接Connection对象线程隔离的例子。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}; public static Connection getConnection() {
return connectionHolder.get();
} public static void setConnection(Connection conn) {
connectionHolder.set(conn);
}
}

  通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是和当前线程绑定的那个Connection对象,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。

附:另一个例子

public class TestNum {
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue() {
return 0;
}
}; public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
} public static void main(String[] args) {
TestNum sn = new TestNum(); //三个线程共享SN 产生序列号
ThreadClient t1 = new ThreadClient(sn);
ThreadClient t2 = new ThreadClient(sn);
ThreadClient t3 = new ThreadClient(sn);
t1.start();
t2.start();
t3.start();
}
} class ThreadClient extends Thread{
private TestNum sn ; public ThreadClient(TestNum sn){
this.sn = sn;
}
public void run(){
for(int i = 0 ; i < 3 ; i++){
System.out.println("Thread: "+ Thread.currentThread().getName()
+ " sn: " + sn.getNextNum());
}
}
}

二、知其所以然

那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:

    /**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
  在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

  线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

为了加深理解,我们接着看上面代码中出现的getMapcreateMap方法的实现:

/**
* 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;
} /**
* 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);
}

代码已经说的非常直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:

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

那么接下来再看一下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();
}

再来看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;
}  

这两个方法的代码告诉我们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

设置到这些线程中的隔离变量,会不会导致内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题。在多个线程中隔离的变量,光荣的生,合理的死,真是圆满,不是么?

最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许,也只能创建一个,那就没有别的办法了,老老实实的使用同步机制(synchronized)来访问吧。

http://my.oschina.net/lichhao/blog/111362

Java并发编程--理解ThreadLocal的更多相关文章

  1. Java并发编程:ThreadLocal

    Java并发编程:深入剖析ThreadLocal   Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用 ...

  2. java并发编程学习: ThreadLocal使用及原理

    多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点. 先来看一下示例: package yjmyz ...

  3. Java并发编程:ThreadLocal的使用以及实现原理解析

    前言 前面的文章里,我们学习了有关锁的使用,锁的机制是保证同一时刻只能有一个线程访问临界区的资源,也就是通过控制资源的手段来保证线程安全,这固然是一种有效的手段,但程序的运行效率也因此大大降低.那么, ...

  4. Java并发编程:深入剖析ThreadLocal(转载)

    Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...

  5. (转)Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...

  6. [转载]Java并发编程:深入剖析ThreadLocal

                原文地址:http://www.cnblogs.com/dolphin0520/p/3920407.html 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨 ...

  7. 【转载】 Java并发编程:深入剖析ThreadLocal

    原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...

  8. Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理

    1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...

  9. 7、Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLoc ...

随机推荐

  1. hdu 3681 Prison Break

    http://acm.hdu.edu.cn/showproblem.php?pid=3681 题意:一个n*m的矩阵,'F'是起点.机器人从F出发,走到G可以充电,走到Y关掉开关,D不能走进,要求把所 ...

  2. 用PYTHON实现将电脑里的所有文件按大小排序,便于清理

    嘿嘿,慢慢找到写代码的感觉了. 这个小程序涉及的东东还是很多的,数据结构的设计,错误的处理,快速字典排序,文件数值调整.... import os,os.path import glob SUFFIX ...

  3. P0口上拉电阻选择

    如果是驱动led,那么用1K左右的就行了.如果希望亮度大一些,电阻可减小,最小不要小于200欧姆,否则电流太大:如果希望亮度小一些,电阻可增大,增加到多少呢,主要看亮度情况,以亮度合适为准,一般来说超 ...

  4. 关于 "Context" 模式(基于COM思想IUnknown思想)

    有同事很喜欢用Context模式,觉得是自己"首创", 我有些自己的想法, 或者大家可以发表下自己的观点.   什么是Context模式? 23种设计模式中没有这个模式, 是同事自 ...

  5. rsyslog 读取单个文件测试

    rsyslog 测试(rsyslog 必须yum 安装uat-web02:/root# rpm -qa | grep rsyslog rsyslog-8.21.0-1.el6.x86_64) //读取 ...

  6. HDU_2051——十进制到二进制转换

    Problem Description Give you a number on base ten,you should output it on base two.(0 < n < 10 ...

  7. 求一个数组中第K小的数

    面试南大夏令营的同学说被问到了这个问题,我的第一反应是建小顶堆,但是据他说用的是快排的方法说是O(n)的时间复杂度, 但是后来经过我的考证,这个算法在最坏的情况下是O(n^2)的,但是使用堆在一般情况 ...

  8. 消除JavaScript闭包的一般方法

    JavaScript 的闭包是一个其主动发展的特性, 也是一个被动发展的特性. 也就是说, 一方面, JS 有了闭包能更好解决一些问题. 另一方面, JS 为了解决某些问题, 而不得不使用闭包勉强来解 ...

  9. HTML5 API's (Application Programming Interfaces)

    New HTML5 API's (Application Programming Interfaces) The most interesting new API's are: HTML Geoloc ...

  10. 10、Cocos2dx 3.0游戏开发找小三之容器篇:Vector、Map、Value

    重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27705613 容器 3.0版本号之前Cocos2d- ...