彻底理解ThreadLocal

来源:互联网 发布:淘宝卖衣服代理兼职 编辑:程序博客网 时间:2024/06/12 23:23

彻底理解ThreadLocal

 276029人阅读 评论(144) 收藏 举报
 分类:
 

目录(?)[+]

ThreadLocal是什么

  早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

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

  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

ThreadLocal的接口方法

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。

  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

[java] view plain copy
 print?
  1. package com.test;  
  2.   
  3. public class TestNum {  
  4.     // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  
  5.     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
  6.         public Integer initialValue() {  
  7.             return 0;  
  8.         }  
  9.     };  
  10.   
  11.     // ②获取下一个序列值  
  12.     public int getNextNum() {  
  13.         seqNum.set(seqNum.get() + 1);  
  14.         return seqNum.get();  
  15.     }  
  16.   
  17.     public static void main(String[] args) {  
  18.         TestNum sn = new TestNum();  
  19.         // ③ 3个线程共享sn,各自产生序列号  
  20.         TestClient t1 = new TestClient(sn);  
  21.         TestClient t2 = new TestClient(sn);  
  22.         TestClient t3 = new TestClient(sn);  
  23.         t1.start();  
  24.         t2.start();  
  25.         t3.start();  
  26.     }  
  27.   
  28.     private static class TestClient extends Thread {  
  29.         private TestNum sn;  
  30.   
  31.         public TestClient(TestNum sn) {  
  32.             this.sn = sn;  
  33.         }  
  34.   
  35.         public void run() {  
  36.             for (int i = 0; i < 3; i++) {  
  37.                 // ④每个线程打出3个序列值  
  38.                 System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  
  39.                          + sn.getNextNum() + "]");  
  40.             }  
  41.         }  
  42.     }  
  43. }  


 通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:

thread[Thread-0] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[3]
thread[Thread-2] --> sn[3]

考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。


Thread同步机制的比较

  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

  Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:

通通透透理解ThreadLocal

  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

  下面的实例能够体现Spring对有状态Bean的改造思路:

代码清单3 TestDao:非线程安全

[java] view plain copy
 print?
  1. package com.test;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.SQLException;  
  5. import java.sql.Statement;  
  6.   
  7. public class TestDao {  
  8.     private Connection conn;// ①一个非线程安全的变量  
  9.   
  10.     public void addTopic() throws SQLException {  
  11.         Statement stat = conn.createStatement();// ②引用非线程安全变量  
  12.         // …  
  13.     }  
  14. }  



由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

代码清单4 TestDao:线程安全

[java] view plain copy
 print?
  1. package com.test;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.SQLException;  
  5. import java.sql.Statement;  
  6.   
  7. public class TestDaoNew {  
  8.     // ①使用ThreadLocal保存Connection变量  
  9.     private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();  
  10.   
  11.     public static Connection getConnection() {  
  12.         // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  
  13.         // 并将其保存到线程本地变量中。  
  14.         if (connThreadLocal.get() == null) {  
  15.             Connection conn = getConnection();  
  16.             connThreadLocal.set(conn);  
  17.             return conn;  
  18.         } else {  
  19.             return connThreadLocal.get();// ③直接返回线程本地变量  
  20.         }  
  21.     }  
  22.   
  23.     public void addTopic() throws SQLException {  
  24.         // ④从ThreadLocal中获取线程对应的Connection  
  25.         Statement stat = getConnection().createStatement();  
  26.     }  
  27. }  


  不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

  当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。



ConnectionManager.java

[java] view plain copy
 print?
  1. package com.test;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.SQLException;  
  6.   
  7. public class ConnectionManager {  
  8.   
  9.     private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
  10.         @Override  
  11.         protected Connection initialValue() {  
  12.             Connection conn = null;  
  13.             try {  
  14.                 conn = DriverManager.getConnection(  
  15.                         "jdbc:mysql://localhost:3306/test""username",  
  16.                         "password");  
  17.             } catch (SQLException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.             return conn;  
  21.         }  
  22.     };  
  23.   
  24.     public static Connection getConnection() {  
  25.         return connectionHolder.get();  
  26.     }  
  27.   
  28.     public static void setConnection(Connection conn) {  
  29.         connectionHolder.set(conn);  
  30.     }  
  31. }  


java.lang.ThreadLocal<T>的具体实现

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

[java] view plain copy
 print?
  1. /** 
  2.     * Sets the current thread's copy of this thread-local variable 
  3.     * to the specified value.  Most subclasses will have no need to 
  4.     * override this method, relying solely on the {@link #initialValue} 
  5.     * method to set the values of thread-locals. 
  6.     * 
  7.     * @param value the value to be stored in the current thread's copy of 
  8.     *        this thread-local. 
  9.     */  
  10.    public void set(T value) {  
  11.        Thread t = Thread.currentThread();  
  12.        ThreadLocalMap map = getMap(t);  
  13.        if (map != null)  
  14.            map.set(this, value);  
  15.        else  
  16.            createMap(t, value);  
  17.    }  

在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。


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


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

[java] view plain copy
 print?
  1. /** 
  2.  * Get the map associated with a ThreadLocal. Overridden in 
  3.  * InheritableThreadLocal. 
  4.  * 
  5.  * @param  t the current thread 
  6.  * @return the map 
  7.  */  
  8. ThreadLocalMap getMap(Thread t) {  
  9.     return t.threadLocals;  
  10. }  
  11.   
  12. /** 
  13.  * Create the map associated with a ThreadLocal. Overridden in 
  14.  * InheritableThreadLocal. 
  15.  * 
  16.  * @param t the current thread 
  17.  * @param firstValue value for the initial entry of the map 
  18.  * @param map the map to store. 
  19.  */  
  20. void createMap(Thread t, T firstValue) {  
  21.     t.threadLocals = new ThreadLocalMap(this, firstValue);  
  22. }  

接下来再看一下ThreadLocal类中的get()方法:

[java] view plain copy
 print?
  1. /** 
  2.  * Returns the value in the current thread's copy of this 
  3.  * thread-local variable.  If the variable has no value for the 
  4.  * current thread, it is first initialized to the value returned 
  5.  * by an invocation of the {@link #initialValue} method. 
  6.  * 
  7.  * @return the current thread's value of this thread-local 
  8.  */  
  9. public T get() {  
  10.     Thread t = Thread.currentThread();  
  11.     ThreadLocalMap map = getMap(t);  
  12.     if (map != null) {  
  13.         ThreadLocalMap.Entry e = map.getEntry(this);  
  14.         if (e != null)  
  15.             return (T)e.value;  
  16.     }  
  17.     return setInitialValue();  
  18. }  

再来看setInitialValue()方法:

[java] view plain copy
 print?
  1. /** 
  2.     * Variant of set() to establish initialValue. Used instead 
  3.     * of set() in case user has overridden the set() method. 
  4.     * 
  5.     * @return the initial value 
  6.     */  
  7.    private T setInitialValue() {  
  8.        T value = initialValue();  
  9.        Thread t = Thread.currentThread();  
  10.        ThreadLocalMap map = getMap(t);  
  11.        if (map != null)  
  12.            map.set(this, value);  
  13.        else  
  14.            createMap(t, value);  
  15.        return value;  
  16.    }  

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


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


小结

  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

ConnectionManager.java

[java] view plain copy
 print?
  1. package com.test;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.SQLException;  
  6.   
  7. public class ConnectionManager {  
  8.   
  9.     private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
  10.         @Override  
  11.         protected Connection initialValue() {  
  12.             Connection conn = null;  
  13.             try {  
  14.                 conn = DriverManager.getConnection(  
  15.                         "jdbc:mysql://localhost:3306/test""username",  
  16.                         "password");  
  17.             } catch (SQLException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.             return conn;  
  21.         }  
  22.     };  
  23.   
  24.     public static Connection getConnection() {  
  25.         return connectionHolder.get();  
  26.     }  
  27.   
  28.     public static void setConnection(Connection conn) {  
  29.         connectionHolder.set(conn);  
  30.     }  
  31. }  

后记

  看到网友评论的很激烈,甚至关于ThreadLocalMap不是ThreadLocal里面的,而是Thread里面的这种评论都出现了,于是有了这个后记,下面先把jdk源码贴上,源码最有说服力了。

[java] view plain copy
 print?
  1. /** 
  2.      * ThreadLocalMap is a customized hash map suitable only for 
  3.      * maintaining thread local values. No operations are exported 
  4.      * outside of the ThreadLocal class. The class is package private to 
  5.      * allow declaration of fields in class Thread.  To help deal with 
  6.      * very large and long-lived usages, the hash table entries use 
  7.      * WeakReferences for keys. However, since reference queues are not 
  8.      * used, stale entries are guaranteed to be removed only when 
  9.      * the table starts running out of space. 
  10.      */  
  11.     static class ThreadLocalMap {...}  

  源码就是以上,这源码自然是在ThreadLocal里面的,有截图为证。


  本文是自己在学习ThreadLocal的时候,一时兴起,深入看了源码,思考了此类的作用、使用范围,进而联想到对传统的synchronize共享变量线程安全的问题进行比较,而总结的博文,总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。


(全文完)






153
28
 
 

  相关文章推荐
  •  java多线程模式ThreadLocal原理简述及其使用详解
  •  大数据技术实战线上峰会--董西成
  •  ThreadLocal工作原理
  •  30天系统掌握机器学习--唐宇迪
  •  JAVA中进程、线程
  •  ORACLE数据库学习资料集锦
  •  搜狐笔试:Kolakoski sequence
  •  PHP从零开始实战篇
  •  JAVA多线程
  •  玩转微信小程序第一篇
  •  [Java并发包学习七]解密ThreadLocal
  •  深度学习案例分享—人脸检测
  •  [Java并发包学习七]解密ThreadLocal
  •  深入剖析ThreadLocal实现原理以及内存泄漏问题
  •  ThreadLocal的底层实现原理与应用场景
  •  面试题目整理
查看评论
92楼 javaPie 刚刚发表 [回复]
这么多评论 看着真TM累
91楼 无敌的小末 5天前 15:42发表 [回复]
不能说原创吧。。从《精通spring4.x》第11章spring的事务管理中摘下来的吧。。。。
90楼 云卷沐风 2017-09-12 23:53发表 [回复]
https://github.com/MinJeromeXU/ThreadLocal
89楼 云卷沐风 2017-09-12 23:14发表 [回复]
测试过了ThreadLocal不能解决并发问题,你举得那个序列号的例子没有出现错误因为变量都是在各自线程中,相当于一个三个方法的三个局部变量做++操作,当然不会相互响应了,每个线程中传递同一个对象,然后通过线程间交换数据的exchanger交换两个线程的对象,你发现是同一个。或者比如A t1 = new A(Obj,seqNum,exgr);A2 t2 = new A2(Obj,seqNum,exgr),在其中一个线程修改Obj,另外一个线程的Obj也会发生变化。
Re: baidu_34082846 57分钟前发表 [回复]
回复AngeloWolf:那个不是静态的吗,怎么就成三个局部变量了
88楼 曹学亮 2017-08-31 19:39发表 [回复]
学习了。
87楼 hobn 2017-08-31 18:27发表 [回复]
虽然看的有的懵,但还是坚持看完啦
86楼 cjr0417 2017-08-30 23:38发表 [回复]
挖一下,感谢分享
85楼 韦文文 2017-08-29 09:21发表 [回复]
很有帮助的博客
84楼 走出自己的未来 2017-08-22 11:02发表 [回复]
很好的总结,能学到不少内容
83楼 huke08093446 2017-08-06 21:59发表 [回复]
这总结的前半部分和某本书的内容几乎一样,也就不说啥了
82楼 ZacReno 2017-07-20 14:44发表 [回复]
如果觉得楼主说的有问题可以去看看这个:**********http://blog.csdn.net/zhaoxin106324/article/details/50913809***************
81楼 you_are_the_one 2017-06-22 10:30发表 [回复]
逆风奔跑。。不要去考虑什么天赋异禀,一切都来自经历和渴望。 写的真不错
80楼 winwill2012 2017-06-13 19:28发表 [回复]
http://blog.csdn.net/winwill2012/article/details/71625570
推荐一篇文章
79楼 我是周洲 2017-06-09 20:53发表 [回复]
总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。
很经典
78楼 与天001 2017-05-19 09:57发表 [回复]
threadlocal 个人感觉就是为了单例模式不出现并发错误的一种每个线程的单例模式
77楼 K0000000r 2017-04-12 23:22发表 [回复]
你得后记还是说错了呀。ThreadLocalMap这个类是定义在ThreadLocal里,但是ThreadLocalMap的实例是被每个线程自己持有的,Thread类中有这样的代码:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Re: K0000000r 2017-04-12 23:47发表 [回复]
回复K0000000r:实际上是每个线程自己持有一个ThreadLocalMap实例,ThreadLocalMap内部是一个类似散列表的东西,key(或者说索引更合适)是通过ThreadLocal实例的threadLocalHashCode算出来的,value是具体的内容实例,实际上ThreadLocal实例本身并不存储内容,只是充当了一个索引的作用。
76楼 华仔的逆袭 2017-04-03 10:02发表 [回复]
JDK在Thread中定义的ThreadLocalMap成员变量注释说明了问题:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
<font color=red>ThreadLocal值附属于这个线程,map的通过ThreadLocal类来进行维护</font>
75楼 epan8588 2017-03-25 22:32发表 [回复]
如果细心的读源码就会发现,Thread类里面确实有ThreadLcoalMap的引用,所以每次拿值的时候都是从自己的Map里面拿的,所以才不会有并发问题。
至于getMap确实是从Thread那里拿的引用来用的,但是如果拿到的map为空的时候,这个时候就要调用createMap(Thread t, T firstValue)方法,这个方法里面就是初始化当前线程里面的ThreadLocalMap,具体操作是

t.threadLocals = new ThreadLocalMap(this, firstValue);

这个方法是在ThreadLocal中的。也就是说第一个ThreadLocal变量在设置值的时候初始化了当前线程的ThreadLocalMap,后面的ThreadLocal变量,直接用初始化好的Map来存储。
当然还有其他的方法初始化ThreadLocalMap
74楼 蜗牛飞了 2017-03-24 11:25发表 [回复]
赞一个,指出错误的人值得赞赏,但是表达的方式过于强硬,有错误指出就行了啊,本来就是分享和交流的平台。反正我觉得这篇文章很不错,就算有错误,也比别人讲的完全看不懂的要好。Spring书本里面介绍这个ThreadLocal我是没有看懂,
73楼 tantan25846 2017-03-22 10:46发表 [回复]
看到后记我就有话说了,关于ThreadLocalMap不是ThreadLocal里面的,而是Thread里面的,楼主的意思是ThreadLocalMap这个类是ThreadLocal的一个内部类,但是我想说在ThreadLocal的get方法中:

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

这里有个getMap(t)方法是获取ThreadLocalMap ,那我们来看看这个getMap(t)是从哪里拿到的:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这里的t从get() 方法的第一行可以是当前线程,也就是说,
ThreadLocalMap map = getMap(t);这一行代码是在从当前线程中获取ThreadLocalMap 的实例对象,所以说ThreadLocalMap这个类虽然是定义在 ThreadLocal中,是ThreadLocal的内部类,但是ThreadLocal自身并没有ThreadLocalMap 的实例对象,ThreadLocal他用到的ThreadLocalMap对象是从当前线程中获取的,获取的是当前现场Thread的一个成员变量
72楼 billdavidup2015 2017-03-21 09:49发表 [回复]
看了楼主的文章,首先要表扬,能自己研究源码再总结出自己的理解很不错,又看了大家的评论,发现的确其中有些谬误。虽然CSDN我认识了2年,也下载过一些开源的代码和工具,还有一些文章。却第一次被CSDN的开源社区惊艳到了,那就是大家的踊跃发言,对于提高楼主和所有看过这篇文章和评论的人都会是一个巨大的提升。17楼链接的文章直接指出了你的谬误,大家可复制链接去阅读。最后谢谢楼主还有各位指出谬误的大神,我替大家谢谢你们!
Re: 枫之逆 2017-03-21 12:59发表 [回复]
回复billdavidup2015:感谢哥们你翻完了所有评论并发现了17楼的精彩认知,其实17楼的那哥们讲的还凑合儿,排榜啥的也比csdn提供的强一些,TreadLocal这玩意儿其实就是每个线程本地存了一个map,避免多线程引发的问题。
71楼 billdavidup2015 2017-03-21 09:47发表 [回复]
看了楼主的文章,首先要表扬,能自己研究源码再总结出自己的理解很不错,又看了大家的评论,发现的确其中有些谬误。虽然CSDN我认识了2年,也下载过一些开源的代码和工具,还有一些文章。却第一次被CSDN的开源社区惊艳到了,那就是大家的踊跃发言,对于提高楼主和所有看过这篇文章和评论的人都会是一个巨大的提升。17楼链接的文章直接指出了你的谬误,大家可复制链接去阅读。最后谢谢楼主还有各位指出谬误的大神,我替大家谢谢你们!
70楼 pp95haha158791 2017-02-15 17:24发表 [回复]
不错的文章。不过这评论现在看来不是很给力,不知道是不是博主当时写的时候真的有那么多的问题。
69楼 香香 2017-02-07 17:35发表 [回复]
我的理解,ThreadLocal是可以解决多线程资源共享所带来的安全问题的,而ThreadLocal本身的实现方式并不是资源共享的,而是为其创建一个单独的副本。这样说有毛病没?
68楼 葫爷爷 2017-01-23 10:55发表 [回复]
建议看Looper
67楼 ldx_heart 2017-01-19 18:41发表 [回复]
mark! ! !
66楼 ruanjianerban10 2016-12-26 17:08发表 [回复]
介绍的思路很棒,就是一些细节上有些硬伤
65楼 nostalie风暴烈酒 2016-12-22 17:19发表 [回复]
老哥,你真的理解错了!!!TreadLocal和“解决多线程中相同变量的访问冲突问题”真的没有关系!!!
Re: you_are_the_one 2017-06-22 12:46发表 [回复]
回复u014798233:你说的对,它跟多线程是没啥关系,也不存在线程共享的变量。如果是共享的引用性变量,就算你存储在threadlocal中,也是会出现数据安全问题的
64楼 maxxcemon 2016-11-02 09:28发表 [回复]
Thread中有一个成员属性:
/*
* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal本身使用Map也是通过当前线程去获取这个map的...它自身没有维护map的引用(更多的是维护了一个自身对应的哈希码)...

所以评论中说的ThreadLocalMap不是Local中的.也是有道理的.只是不严谨.

推荐一个:
http://songuooo.com/2014/8/10/threadlocal-why-0x61c88647
63楼 roamer_nuptgczx 2016-10-04 16:08发表 [回复]
博主为什么一直不纠正这篇文章?很多同学已经指出博文中对ThreadLocal的理解错误了。关于博主的后记部分,只能说明博主确实看了源码,但是没有继续深入地看下去。对ThreadLocal正确的理解只需要看17楼给的博文就行,我当初也是看了这篇文章才彻底明白的。
Re: iciscosystem 2017-03-28 18:19发表 [回复]
回复roamer_nuptgczx:因为博主的这篇文章是照抄的,《spring 3.x 企业应用开发实战》
Re: gyj1278 2016-12-27 21:36发表 [回复]
回复roamer_nuptgczx:为什么我找了半天也没找到17楼的博文,
62楼 dafudu123 2016-09-30 10:26发表 [回复]
很少评论,但是这个文章真是误人子弟,跟同步没有半毛钱.
61楼 屌丝的烦恼 2016-09-26 10:55发表 [回复]
如果多个线程同时 set 同一个 value,操作这个 value ,怎么做到的线程之间相互没有影响。value 难道不是个引用传递吗?请解释一下,这个问题纠结了好几天了。
Re: chenyeisme 2016-11-09 16:46发表 [回复]
回复u014313064:求解,我也有同样的困惑
Re: jbscuthua 2017-02-23 15:19发表 [回复]
回复chenyeisme:我的理解是:value的使用有没有影响取决于initialValue()的实现方式。如果initialValue每次都new一个新的对象返回,那么不同线程获得的value就不一样,相互之间也就没有影响;如果initialValue每次都返回同一个对象,比如说一个静态变量,这时候大家都使用同一个对象,就会有冲突。
Re: fri_yu 2016-12-22 15:15发表 [回复]
回复chenyeisme:每个thread维护自己的map对象,并非多个thread公用一个map对象。
通过查看源码可知,在调用set 与 get 的时候,都会根据当前线程,返回当前线程的ThreadLocalMap对象,如果没有,则new一个,以便之后使用。
public class Thread implements Runnable {
xxxxxxxx;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
xxxxx
}
get 与set 的时候,都是获取的thread里面的,每个线程维护一份
60楼 ericjauy 2016-09-12 17:59发表 [回复]
学习了,提供了另一种解决多线程的思路
59楼 striverpan 2016-08-25 23:57发表 [回复]
ThreadLocal 跟同步 共享 没有关系吧~
Re: 俺就不起网名 2016-09-09 15:42发表 [回复]
回复w616358337:怎么没关系?
58楼 zejian_ 2016-08-23 08:52发表 [回复]
开头就有问题了吧,Threadlocal里面没有Map。。。。。是个数组才对吧
Re: 末树之墨 2016-08-26 10:12发表 [回复]
回复javazejian:ThreadLocal里根本就啥也没有啊啊啊啊,那只是个定义啊啊啊啊,真正持有这个map对象的是Thread类
Re: zhangle789 2016-08-23 14:37发表 [回复]
回复javazejian:java 的是map 
android 的是数组
57楼 u014407873 2016-08-16 15:00发表 [回复]
博主对ThreadLocal理解错了,ThreadLocal不能解决线程安全问题,它可以理解成是一个容器,为线程持有变量的容器,如果多个线程在ThreadLocal中持有同一个变量依然存在线程安全性问题。
Re: 末树之墨 2016-08-26 10:14发表 [回复]
回复u014407873:大哥。。首先博主不是理解错了,而是从头到尾完全讲错了,ThreadLocal源码根本不是博主说的那样。然后你的理解也有一些偏差,仔细看一下源码你就能明白到底怎么回事了。
56楼 末树之墨 2016-08-04 19:46发表 [回复]
ThreadLocalMap确实是ThreadLocal里的内部类,但是threadlocal的真正工作原理在于Thread类里持有了一个ThreadLocalMap。

而且ThreadLocalMap的键是ThreadLocal而不是Thread!您仔细看一下好吗?

我研究这个threadlocal时,百度一搜,第一个看的就是你这个,真是坑啊,希望您重新看一下这个知识点,修正自己的文章!

正确的解释就在源码中,和这篇文章:http://www.cnblogs.com/dolphin0520/p/3920407.html
Re: qq_22105541 2016-08-12 14:28发表 [回复]
回复u013303743:我去 多谢 多谢 我自己去看了一下源码 debug跑了一下 发现博主说的真是错的 = =
Re: 末树之墨 2016-08-14 20:41发表 [回复]
回复qq_22105541:http://blog.csdn.net/cryssdut/article/details/52123142,我写的文章,希望对你有帮助~
55楼 normalperson_s 2016-07-30 12:15发表 [回复]
ThreadLocal的关键点就在于用到了Thread.currentThread(),因为每个线程都是独一无二的。
54楼 no_do_no_die 2016-07-29 09:31发表 [回复]
最近看lucene的源代码,然后看到有CloseableThreadLocal这一个类,看了源代码,发现是对ThreadLocal类的一个改进,里面写到,由于所有的线程共享着一个threadlocalmap,如果threadlocalmap的对象是强引用的话,如果碰上内存吃紧一点的情况,很有可能会出现OutOfMemoryError的情况,CloseableThreadLocal类于是将ThreadLoca的T用weakReference<T>这个形式进行了替换,从而保证了当没有其他引用引用这个对象时,能够保证对象被正常销毁
53楼 lzl__lzl 2016-07-21 09:55发表 [回复]
楼主害人不浅啊,写了很多,但是其实是错误的,我刚看到开头楼主说每个ThreadLocal中都有一个Map...我就感觉不对头,去看了其他文章回来发现楼主根本就搞错了
Re: caimofei 2016-07-27 08:43发表 [回复]
Threadlocal的原理并不是每一个threadlocal都有一个map。而是在每一个thread里都有一个map。用于保存该线程持有的线程局部变量。这个map的键是threadlocal对象,值是变量的值
Re: helloworldsss 2016-07-24 01:49发表 [回复]
回复lzl__lzl:6666666666
52楼 年青人阿奔 2016-07-09 12:33发表 [回复]
觉得 http://www.iteye.com/topic/103804 这个文章讲的比较正确。存的确实是线程各自的私有变量,跟共享什么的好像没什么关系。
51楼 tory_you 2016-07-08 16:52发表 [回复]
这段代码有问题;麻烦楼主看下!
50楼 zhncug 2016-07-05 09:53发表 [回复]
楼主写了这么多但还是半瓶水没搞清楚,threadlocalmap是thread对象的属性,只是定义在threadlocal类中
49楼 Evj86 2016-07-03 06:10发表 [回复]
最新想通过自定义注解的方式设计一个性能分析工具,思想就是aop,以前没怎么弄过这个东西,只是大概知道一些思想,就是在不影响原有的业务逻辑的情况下,可以对某个服务方法,在发生前或发生后有一个监控,before,after相信这两个东西大家都非常有印象,在实际的测试当中,我发现自定义的注解类是一个单例的,这个不难理解,因为为了节省资源开销,SPRING大部分都用了单例,那么问题来了,类变量即是公共变量,方法肯定是多线程的,多线程调用公共变量,那必然可以修改公共变量的值,谁都可以修改,那么大伙(多线程)拿到的值总是会被别的线程修改掉的,这个时候我才把注意力放在了ThreadLocal上面,因为写代码这玩意,既然能写出来,必然有它一定的意义,OK,带我刚才所构想的疑问,再加上我搜索ThreadLocal相关资料,隐约感觉到这个ThreadLocal这个东西就是保存着当前线程对应的值,我讲了,方法永远是多线程的,公共变量如果也是多线程的,而且只能被当前的方法线程所调用,那么肯定是可以隔离开的,通俗的讲,就是自己的线程调用自己的公共变量,别的线程无权干涉,有人说用static,我想问真的有必要吗?自定义注解类是单例的,类属性new成功后面就不会再改变,是否可以这样认为呢!
还有一个很有利的证据就是ThreadLocalMap 的KEY用的是不同的线程,因为每次调用的方法肯定能产生不能的线程,所以这就不难解释为什么每次调用的方法可以取到自己对应的类属性的值。
至于ThreadLocal与多线程的关系,我开始讲了,每个方法的调用就会产生一个新的线程,所以每个线程想要拿到自己对应的公共变量的值,肯定不能用类似private String abc; 这样的类公共属性,你想呀,类是单例的,势必谁都会修改abc里面的值,但又没有其它的解决办法,因为before跟 after是处于两个不同的方法,用局部变量,两个方法之间没法传递,用公共变量又会被其它线程所修改,所以ThreadLocal作用就在于,即是公共变量,又只能被自己的线程所修改调用,如此这般,比如在性能方面,before方法里面记录一个开始时间放在ThreadLocal里面,然后再after方法里面获取结束之间,两个时间之差即是所消耗时间,perfect,希望我的理解对大家有所帮助,不妥之处多多欢迎沟通交流,3Q
Re: Evj86 2016-07-03 06:26发表 [回复]
回复Evj86:@Aspect
@Component
public class AnalysisAop {

private ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private ThreadLocal<String> id = new ThreadLocal<String>();
private ThreadLocal<String> name = new ThreadLocal<String>();

@Before
public void beforeExec(JoinPoint joinPoint){

@After
public void afterExec(JoinPoint joinPoint){
48楼 letcheng_uu 2016-07-02 15:35发表 [回复]
DriverManager这个类,可以再加一个close方法。
public static void closeConnect(){
connectionHolder.get().close(); // 关闭 conn
connectionHolder.remove(); // 移除 ThreadLocal
}
47楼 清晨Feelter 2016-06-27 18:55发表 [回复]
知识之上是思想;
说的真好
46楼 xsbry 2016-06-23 15:16发表 [回复]
其实不与同步放在一起讲,还是篇好文章。同步就是要共享变量,如果没有共享变量,那么就没有同步要求。
45楼 wodeqiantu2010 2016-06-22 16:49发表 [回复]
大方向理解错了。这个跟线程并发没有关系。
44楼 wodeqiantu2010 2016-06-22 16:44发表 [回复]
我只能说楼主分析的还可以,但是大的方向错了,ThreadLocal根本不是为了解决多线程并发问题,仅仅是是提供了每个线程保存自己变量的一种途径而已,真正多线程共享变量的需求是必须通过锁来解决的。
43楼 wodeqiantu2010 2016-06-22 16:43发表 [回复]
我只能说楼主分析的还可以,但是大的方向错了,ThreadLocal根本不是为了解决多线程并发问题,仅仅是是提供了每个线程保存自己变量的一种途径而已,真正多线程共享变量的需求是必须通过锁来解决的。
42楼 wodeqiantu2010 2016-06-22 16:42发表 [回复]
我只能说楼主分析的还可以,但是大的方向错了,ThreadLocal根本不是为了解决多线程并发问题,仅仅是是提供了每个线程保存自己变量的一种途径而已,真正多线程共享变量的需求是必须通过锁来解决的。
41楼 博予liutxer 2016-06-15 23:17发表 [回复]
分析的不错,有疑问的建议去看源码。
40楼 阿骆麦迪 2016-06-13 18:21发表 [回复]
理解理解不深入,或者说表达很有问题,反而误导人,摘取了几句话,1.ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题;2.在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量,而ThreadLocal则从另一个角度来解决多线程的并发访问
----------------------------------------------------
一二两句话在定义上就错了,两个解决的根本不是一个问题,而不是像楼主这句话表达的都是解决相同变量的访问冲突,就像是比较一个人吃饭和上学一样,虽然涉及的主体都是人(线程),但一个是为了更好地吃饭,一个是为了更好地上学,根本没有可比性。
废话说了这么多,例子也不恰当,其实就一句话,两个东西根本不是一类东西,没有可比性,拿来比较本身就是错误
39楼 林祥纤 2016-06-04 14:56发表 [回复]
很不错,写的很棒。
38楼 小时候的阳光 2016-06-02 20:58发表 [回复]
ThreadLocalMap确实是ThreadLocal中的静态内部类,Thread中局部对象包括这个ThreadLocalMap。

ThreadLocal 的工作原理在于:每个Thread调用到ThreadLocal 的set ( T )方法时,其实内部实现的就是ThreadLocal 对象创建一个ThreadLocalMap对象塞到调用的那个Thread局部对象中。真正保存的数值都存在每个Thread自己那边,当然线程之间没什么影响啊。

说明下ThreadLocalMap 中保存的值:类似于 Map(key,value) 保存的,key=ThreadLocal 对象,value=你传递的值对象。 这个ThreadLocalMap 保存在每个调用的Thread那边!
37楼 az4697020 2016-05-26 09:29发表 [回复]
大哥,你代码清单4 TestDao:线程安全那部分的代码你确定没问题?递归调用getConnection()方法,每次都是null,直接报java.lang.StackOverflowError,你看看。
36楼 ashui811 2016-05-10 23:15发表 [回复]
关键就是共享这个词容易误导,应该叫copy副本,举个简单例子,售票系统你用ThreadLocal能解决并发问题不?
35楼 胡小圣 2016-04-23 11:38发表 [回复]
没看后面,但开头错了,楼主说:“在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本”,应该是每个线程有自己的map,键值是ThreadLocal,值为变量的副本,这篇文章从源码上分析的很详细:http://www.cnblogs.com/dolphin0520/p/3920407.html
34楼 胡小圣 2016-04-23 11:35发表 [回复]
这篇博文开头就错了,开头作者说“在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本”,应该是每个线程有一个map,键值是ThreadLocal,值为变量的副本,要从源码上分析,可以看这篇文章:http://www.cnblogs.com/dolphin0520/p/3920407.html
33楼 善林 2016-04-13 14:55发表 [回复]
楼主好厉害
32楼 SvenHe 2016-04-03 13:25发表 [回复]
关于ThreadLocal的作用:“ThreadLocal是为了解决多线程中相同变量的访问冲突问题” 和 “ThreadLocal是用来隔离线程存储数据” 这两句话这两句话并没有冲突呀,楼主的本意应该就是:在多线程并发中,ThreadLocal通过隔离数据的存储,解决了多线程变量访问的冲突问题。
31楼 SvenHe 2016-04-03 13:24发表 [回复]
关于ThreadLocal的作用:“ThreadLocal是为了解决多线程中相同变量的访问冲突问题” 和 “ThreadLocal是用来隔离线程存储数据” 这两句话这两句话并没有冲突呀,楼主的本意应该就是:在多线程并发中,ThreadLocal通过隔离数据的存储,解决了多线程变量访问的冲突问题。
30楼 tingyingg 2016-03-30 22:08发表 [回复]
楼主棒棒哒,但有些地方确实描述有点难以让人捉摸,大家多用点心思就能慢慢理解了。比如:总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。

这句话有点含糊,有的可能以为用ThreadLocal 就完全能代替锁机制了。锁机制是内存共享问题,而ThreadLocal是解决代码共享问题。 其实我很崇拜博主,么么哒
29楼 晓风残月xj 2016-01-16 22:03发表 [回复]
楼主,您好。看源码认为:“ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。”
28楼 下雨天没带雨伞 2016-01-15 17:47发表 [回复]
可能还很菜,感觉楼主的理解够用,楼下评论中的有的还不理解。
27楼 kyrie233 2016-01-07 11:52发表 [回复]
幸亏先看了评论~~~~~
26楼 水田如雅 2016-01-04 19:38发表 [回复]
楼下评论好混战~
25楼 raynforu 2015-12-30 11:06发表 [回复]
博主的jdk是哪个版本的?JDK1.8中使用的不再是threadlocalMap了,而是直接存储在线程中的哈希数组localValues。
Re: 枫之逆 2015-12-30 11:22发表 [回复]
回复u012661091:没记错的话,应该是1.7版本。
24楼 lszzzz 2015-12-21 22:25发表 [回复]
写的很清楚,支持!
23楼 lhwbake 2015-11-23 15:02发表 [回复]
22楼 大嘴蛙 2015-11-15 20:24发表 [回复]
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
这句对ThreadLocal实现的理解完全错误,希望网友不要被误导,实际上ThreadLocal类中并没有Map,而是在每个Thread中有各自对应的ThreadLocalMap,每个ThreadLocalMap都是以ThreadLocal实例为键存储变量值的,需要存储多个变量值的话就需要多个ThreadLocal实例。
Re: 枫之逆 2016-01-13 11:54发表 [回复]
回复u012772007:这位网友,你错了,你去看jdk源码,会发现ThreadLocalMap是ThreadLocal的静态内部类,只要看了源码,这是毫无疑问的,你看完源码再来评论好么?
Re: SunnyMarkLiu 2015-12-16 17:37发表 [回复]
回复u012772007:说的没错!但ThreadLocal中的ThreadLocalMap其实就是一个map,或者说原理就是一个map,JDK中的解释是一个定制化的hash map:ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. ThreadLocalMap中包含一个Entry(ThreadLocal<?> k, Object v)以及Entry[]数组,这两个也就实现了map的功能。但是key为ThreadLocal。
Re: 草丛里的码农 2016-02-17 16:55发表 [回复]
回复Mark_LQ:我估计应该是1.7和1.8的区别吧,1.8中看了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;
}
}
21楼 eccel 2015-10-23 15:58发表 [回复]
要彻底理解ThreadLocal-看源码自己最清楚了,楼主理解确实有误差。跟同步毛关系没有。
20楼 mhsfw007 2015-09-29 16:58发表 [回复]
花好长时间看完正文才看到评论,说楼主坑人,我都快哭了,但我想说的是,按照楼主的理解去使用ThreadLocal并无什么不妥,至于太高端的用法就不说了,普通程序一般不会因对原理的理解有偏差而掉进“坑”里吧。对楼主和指出楼主错误的大牛们表示敬意。
19楼 -琥珀川- 2015-09-11 18:07发表 [回复]
今天面试出了关于threadlocal的题
18楼 chang0507 2015-09-04 22:15发表 [回复]
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

看到上面这句话就直奔评论来了
17楼 winwill2012 2015-09-04 01:27发表 [回复]
楼主理解得好像有点点问题,建议大家去看看这篇文章吧
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
比较深入详细地介绍了ThreadLocal的实现原理,并讨论了ThreadLocal到底会不会引发内存泄露。
16楼 qingxili 2015-08-14 11:42发表 [回复]
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要。
Re: 萨满哥 2015-10-09 16:12发表 [回复]
回复qingxili:同意层主
Re: winwill2012 2015-09-04 01:30发表 [回复]
完全同意,楼主理解有点点问题,建议大家去看这篇文章
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
比较详细深入地介绍了ThreadLocal的实现原理,并探讨了ThreadLocal是否真的会引起内存泄露。
Re: winwill2012 2015-09-04 01:29发表 [回复]
完全同意,楼主理解有点点问题,建议大家去看这篇文章
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
比较详细深入地介绍了ThreadLocal的实现原理,并探讨了ThreadLocal是否真的会引起内存泄露。
15楼 qiaoba_gogo 2015-08-04 14:20发表 [回复]
楼主在坑人,楼主写的完全是错的,大家可以自己看源码,就全都明白了,ThreadLocal根本就不是解决共享变量的问题,而是每一个线程会隔离开来存储数据,所以线程之间根本就不会共享变量。所以大家一定要注意ThreadLocal是用来隔离线程存储数据的。但是楼主的奉献精神还是值得表扬的。
Re: Geeker-X 2016-06-14 11:43发表 [回复]
回复u010014658:赞同!
Re: lndhuyang 2016-01-11 15:34发表 [回复]
回复u010014658:仔细看--------概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。-----------

楼主说的“以空间换时间”和你说的"每一个线程会隔离开来存储数据"是一个意思。
Re: wuquangui_2014_job 2015-09-14 23:27发表 [回复]
回复u010014658:
14楼 qiaoba_gogo 2015-08-04 14:19发表 [回复]
楼主在坑人,楼主写的完全是错的,大家可以自己看源码,就全都明白了,ThreadLocal根本就不是解决共享变量的问题,而是每一个线程会隔离开来存储数据,所以线程之间根本就不会共享变量。所以大家一定要注意ThreadLocal是用来隔离线程存储数据的。但是楼主的奉献精神还是值得表扬的。
Re: lndhuyang 2016-01-11 15:32发表 [回复]
回复u010014658:你仔细看了楼主的文章吗? 对这句话你是怎么理解的呢?-----概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。----------- 这和你说的“每一个线程会隔离开来存储数据”完全是同一个意思。只不过楼主的说法是“以空间换时间”。本质没有任何区别。至少本人看了觉得大有帮助,说楼主坑人太离谱了。
Re: 枫之逆 2016-01-13 11:40发表 [回复]
回复lndhuyang:嗯,你说的挺对的,其实就是“用空间换时间”,线程自己存储一份拷贝,所以不会有线程安全问题。
13楼 易碎的泪 2015-07-29 15:38发表 [回复]
lz,我还是没有明白,每个程序该遍了自己的副本,其它的程序不知道,有什么用呀,是不是没有实现数据的共享。
Re: 枫之逆 2016-01-13 14:37发表 [回复]
回复u012967289:不同线程访问数据的时候,是多线程数据共享的问题,本文的解法主要是用空间换时间。
12楼 tyzhsj 2015-07-25 15:42发表 [回复]
写的很好的一篇关于ThreadLocal的博文,感谢博主
11楼 疯狂的赣江 2015-07-09 17:31发表 [回复]
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

在我们大部分的使用都是将ThreadLocal作为一个静态的变量来使用的。因此,楼主这句话很容易让人误解啊,,,切记,不要偷懒,最好用完记得要remove!!!否则,怎么死都不知道!
Re: 尐葮阿譽 2016-03-25 11:32发表 [回复]
回复mar_ljh:楼主说的没错啊!ThreadLocal一般是当静态用,但是不用remove有什么关系呢?remove移除的是Thread实例中的threadLocals中对应的变量,而threadLocals在Thread类中是实例变量,随着线程的消失,Thread实例变量threadLocals及里面所存储的变量会被回收的。建议大家把ThreadLocal类当成一个工具类,它本身并不持有ThreadLocalMap实例变量(ThreadLocalMap只是该工具类中定义的静态内部类),它所操作的ThreadLocalMap实例变量是Thread实例中的threadLocals变量
10楼 mini宝儿 2015-04-06 05:24发表 [回复]
我来看看,面试问了,不会,认真看过之后,在看到5楼的纠正,有点乱啊。+_+
Re: mc46790090 2015-04-28 19:11发表 [回复]
回复oBuTianBuNi:建议看http://www.iteye.com/topic/103804
5楼说的很对,threadlocal压根就不是为了解决变量共享的问题的,只是为了避免同一线程中对象参数传递而提出的。
Re: 走在菜鸟的路上 2015-05-19 09:56发表 [回复]
回复mc46790090:其实题主只是有些概念有些让人误解,但是他的理解没有问题,ThreadLocal提出就是为了解决共享变量的线程同步问题的,对象拷贝只是实现的效果,其内部是通过创建线程内部变量实现的,源码中写道:Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the <tt>ThreadLocal</tt> instance is accessible;
Re: mc46790090 2015-05-27 14:16发表 [回复]
回复liuyeliufeng:@mini宝儿,不好意思,链接是http://www.iteye.com/topic/103804。5楼说的很对,你可以看下。
至于这位兄弟,我想说“同步个卵啊,你自己都翻译出来了‘这个类提供线程内部变量,这些变量和正常变量不同,因为每个线程都有自己并独立初始化的变量拷贝。ThreadLocal实例通常是私有静态变量,并通常和线程的状态有关。’”
每个线程中都有一个独立的threadlocal变量拷贝,你告诉我咋跨线程同步?能不能看看源码啊。。。少年
为博主的分享精神点赞,但是希望博主以后能尽量理解准确之后再发文,不然容易将新手带歪的....如果理解之后好歹把博文改一下哈
Re: IMTOP 2015-07-04 15:30发表 [回复]
回复mc46790090:ThreadLocal实例通常是私有静态变量,并通常和线程的状态有关,其实关键就在这句话,可能ThreadLocal的初衷并不是为了解决对象的共享问题,但是在某些情况下确实适用。因为和线程的状态有关,所以每个线程都生成一个副本,那就可以解决一个对象在并发下的共享问题。个人理解,如果说的不对请指出。
9楼 mini宝儿 2015-04-06 05:22发表 [回复]
我来看看,面试问了,不会,不过认真看完之后,被5楼的纠正弄的有点乱+_+
8楼 chenleiking 2015-02-11 16:23发表 [回复]
结合大家的看法和自己的研究,我举个例子,看看在这种情况下是不是使用ThreadLocal比较贴切:
1、我有一个类,在实例化的时候需要消耗大量的系统资源的和时间,但是在单例模式下又会存在线程安全的问题;
2、尽管在单例模式下会有线程安全的问题,我依然使用单例实现,只是将geInstance方法略作修改,在“geInstance”方法中加入ThreadLocal,每次都从ThreadLocal中get我的对象实例;
3、这样既不用重复繁重的创建对象实例,然后因为ThreadLocal的线程隔离,也不用考虑线程安全问题;
以上是我的理解,不知道是否正确,或存在不足之处,希望大家赐教!
7楼 yinwt111 2015-01-26 15:53发表 [回复]
5楼分析的很正确
Re: zcqshine 2015-06-10 09:22发表 [回复]
回复yinwt111:这样用貌似是木有问题的, 相当于是在单例中为每个线程拷贝了一个副本, 而副本之间互不影响.
6楼 xurui8691413 2015-01-20 22:06发表 [回复]
5楼说的很对,楼主并没有理解threadlocal的真正含义。ThreadLocal是线程范围内的资源共享,并不能在多线程共享资源的情况下使用,因为他会为每个线程创建独立的资源,且不可与其他线程共享
5楼 zhuwhzyjsxy 2014-10-28 14:03发表 [回复]
首先为楼主的分享精神表示敬佩。但是楼主的这个帖子很容易误人子弟~
1.ThreadLocal不是用来解决共享对象的多线程访问问题的,一定要扯上关系,结果就是这样,大家都看不懂你在说什么。
2.ThreadLocal的实现并不是楼主说的在其内部提供了一个Map,用Thread作为key;用Thread作为key就会存在共享变量的同步与并发问题,会对ThreadLocal进行加锁,楼主源码已经很清晰的说明了实现机制,我就不分析了。
3.ThreadLocal并不是为线程保存了对象的副本,ThreadLocal是为每个线程隔离了一个类的实例,这个实例的作用于仅限于线程内部。楼主的“序列号”的例子不够贴切,因为对原始类型的赋值操作都是对这个值的拷贝。楼主可能要说,那我这是Integer类型,怎么说?那我要说的是,原始类型的封装类型在做运算的时候,先解封,再运算,然后把结果重新封装成新的对象,显然,这依然是一个副本,所以,不能说是为线程保存了对象的副本,楼主选择例子一定要慎重。。
4.如果真的涉及到多线程共享的时候,ThreadLocal是解决不了这个问题的,既然作为共享变量,ThreadLocal并不是为线程提供共享变量的副本,而仅仅是为线程隔离对象,那我要问了,为线程隔离共享变量的意义是什么?毫无意义,本来就是共享的变量,隔离的目的是让其他线程无法访问,这已经违背了共享变量的目的了。。对共享变量的读写是需要同步机制实现的。。。
后面我来简单解释一下ThreadLocal的作用~
我们从这个类的名字就能了解到类的作用,ThreadLocal可以分解为Thread,和Local,前者就不多说了,后者的意思是局部,整个类名可以理解为:线程局部对象。程序是运行在线程中的,所以,在整个运行过程中,在任何地方都可以获得这个线程的局部对象,你从线程内部取出的局部对象难道会变成其他线程的对象么?显然不会啊。
Re: 太阳晒屁股了 4天前 19:49发表 [回复]
mark下
Re: qq_23359777 2015-09-29 00:45发表 [回复]
回复zhuwhzyjsxy:层主,这最后一段真的是发人深醒啊!
Re: z282762779 2015-03-18 20:33发表 [回复]
回复zhuwhzyjsxy:5楼我想问一下 你4,5点是支持是副本,还是支持是隔离对象?我没看错的话楼主是觉得是个副本。
Re: 走在菜鸟的路上 2015-05-19 10:09发表 [回复]
回复z282762779:This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized copy of the variable. <tt>ThreadLocal</tt> instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
大概意思翻译一下:
这个类提供线程内部变量,这些变量和正常变量不同,因为每个线程都有自己并独立初始化的变量拷贝。ThreadLocal实例通常是私有静态变量,并通常和线程的状态有关。
4楼 memoryisking 2014-08-01 13:13发表 [回复]
关于更多ThreadLocal的内容可以去看这里:ThreadLocal的源码分析
3楼 何静媛 2014-06-26 21:45发表 [回复]
很棒的threadLocal总结,学习了
2楼 Sup_Heaven 2014-06-16 09:16发表 [回复]
可能太菜,还是不太明白。
据我所知Servlet是单例的,但是是支持多线程的,也就是说多个线程会使用同一个Servlet。虽然这样,线程A使用该Servlet的doservice()是在线程A中,线程B使用该Servlet的doservice()是在线程B中,那么doservice()中的DAO层必然也在同一个线程中。
小弟菜鸟,求LZ耐心解释,谢谢。
Re: 枫之逆 2014-06-16 09:55发表 [回复]
回复Sup_Heaven:你说的线程A和线程B来说,那么他们并发访问的情况下,doservice()中的DAO层必然在同一个线程中么?可能就不在一个线程了,if (connThreadLocal.get() == null) 这里A判断是Null,所以他进去实例化了一个Connection,B这个时候也访问正好A还没实例化完毕,B看到的也是Null,B也去实例化一个Connection,那么就有两个Connection,这就是不同的Connection了。。。
Re: Sup_Heaven 2014-06-16 14:57发表 [回复]
回复lufeng20:怎么感觉越来越乱了,我说说我整体的理解。
假设有请求RA和请求RB,他们都会去访问ServletA(,简称SA),那么这时肯定是在两个线程(假设线程A,B)中同时跑SA。
在SA中有dopost方法,该方法执行某个Service的doservice()方法;
doservice(){
DAO d1=new DAOA();
d1.dodaoa();
DAO d2=new DAOB();
d2.dodaob();
}
那么这样下来的必定每个请求对应一个线程,所以各自的doservice()方法在各自的线程中执行,所以仔线程A中当DAOA会先新建Connection,DAOB也用DAOA新建的Connection,同样在线程B中也是这么个情况。
所以我觉得这句话下面不对,不知道是我哪一步理解错了,号纠结啊。
“当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。“
Re: 枫之逆 2014-06-16 15:02发表 [回复]
回复Sup_Heaven:DAO里面获取collection是共享的 public static Connection getConnection(),“但无法和其它DAO共用同一个Connection”这句说说的线程安全问题就是这个,结合if (connThreadLocal.get() == null) 这里就可能发生线程安全
Re: Sup_Heaven 2014-06-16 16:13发表 [回复]
回复lufeng20:好吧,关键是下,下面的Connection conn = getConnection(); 
if (connThreadLocal.get() == null) { 
Connection conn = getConnection(); 
connThreadLocal.set(conn); 
return conn; 
}
不知道你的getConnection(); 是怎么获得的,如果一个线程中先DAOA,发现该线程没有Connection便从连接池中拿一个并保存在当前线程中,接着同一个线程中的DAOB执行,发现有当前线程已经有Connection,就直接用了,还是同一个啊。
不知道是你和我谈的不是一样的东西,还是我真的想错了?
Re: 枫之逆 2014-06-16 16:56发表 [回复]
回复Sup_Heaven:getConnection()这里的实现当然是最基本底层的的数据库连接来实现。。少年,麻烦你认真看我的回复好么。。“if (connThreadLocal.get() == null) 这里A判断是Null,所以他进去实例化了一个Connection,B这个时候也访问正好A还没实例化完毕,B看到的也是Null,B也去实例化一个Connection,那么就有两个Connection,这就是不同的Connection了” 这里哪儿有连接池??都是自己实现的放在Connection conn = getConnection(); 
connThreadLocal.set(conn); 这里set之后放在threadLocal里面的好么?如果有不止AB,还有CDEFG等同时并发访问,看到的都是Null那么他们都会自己去getConnection,这样就会产生多个了!!!
Re: Sup_Heaven 2014-06-16 17:27发表 [回复]
回复lufeng20:你的回复我当然仔细看了,总觉得我和你说的不是同一个东西,这次放上代码,哈哈。
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection(
url,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);
}
}
public class TestService {
public void doservice() throws SQLException{
Statement stat1 = ConnectionManager.getConnection().createStatement();
stat1.execute("sql1");
Statement stat2 = ConnectionManager.getConnection().createStatement();
stat2.execute("sql2");
}
}
我的意思就是一个线程访问doservice() 方法,里面两次获取(ConnectionManager.getConnection())的connection必然是同一个connection,而不同线程必然获得不同的connection,难道不是这样吗?
Re: 枫之逆 2014-06-16 17:34发表 [回复]
回复Sup_Heaven:你附的代码这个是改进之后的,当然就没问题了,public static Connection getConnection() { 
// ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, 
// 并将其保存到线程本地变量中。 
if (connThreadLocal.get() == null) { 
Connection conn = getConnection(); 
connThreadLocal.set(conn); 
return conn; 
} else { 
return connThreadLocal.get();// ③直接返回线程本地变量 


这个是改进之前的,这里就存在线程安全的问题
Re: Sup_Heaven 2014-06-16 17:56发表 [回复]
回复lufeng20:就算改造之前的,我觉得下面的一个线程在执行doservice() 方法时两次getConnection拿到的也是同一个Connection,因为在该线程内doservice() 方法是被线性执行的。当然每个线程拿到的Connection是不一样的。
public void doservice() {
Statement stat1 = ConnectionManager.getConnection().createStatement();
stat1.execute("sql1");
Statement stat2 = ConnectionManager.getConnection().createStatement();
stat2.execute("sql2");
}
1楼 Sup_Heaven 2014-06-13 17:46发表 [回复]
“当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。“
如果从Action到Service都是一个线程,那么在某个service中有
doservice(){
DAO d1=new DAOA();
d1.dodaoa();
DAO d2=new DAOB();
d2.dodaob();
}
那此时难道他们不再同一个线程中,获取的是不同的Connection,我不太明白,求LZ解释?
Re: zljjava 2015-12-24 18:24发表 [回复]
回复Sup_Heaven: 你的代码说明就是一个线程,但不一定是一个Connection。得跟据 
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); 
在DAO 还是在DAOA/DAOB里。
Re: 枫之逆 2014-06-13 18:12发表 [回复]
回复Sup_Heaven:在一个doservice()里面是同一个,因为他们是同一个线程,但是不同的HTTP请求,对servlet来说都是一个线程,当并发请求的时候,不同线程用doservice()方法来响应,那么就可能有线程安全的问题,获取的可能就是不同的Connection。我说明白了吧?
Re: az4697020 2016-05-26 09:35发表 [回复]
回复大哥,你确定“代码清单4 TestDao:线程安全”部分的代码没问题?递归调用getConnection()方法,每次都是null,直接报java.lang.StackOverflowError了。: