- 浏览: 130465 次
- 性别:
- 来自: ...
文章分类
最新评论
说到ThreadLocal,首先说说这个类的命名。直观上看好像是个Thread的什么亲戚,但其实它想表达的意思是线程本地变量,也就是说每个线程自己的变量。它作为一个JDK5以后支持范型的类,主要是想利用范型把非线程安全的共享变量,封装成绑定线程的安全不共享变量。这样的解释我想我们多半能猜出它的实现思路:把一个共享变量在每个线程使用时,初始化一个副本,并且和线程绑定。以后所有的线程对共享变量的操作都是对线程内部那个副本,完全的线程内部变量的操作。
要实现这样功能类的设计,主要技术点是要能把副本和线程绑定映射,程序可以安全查找到当前线程的副本,修改后安全的绑定给线程。所以我们想到了Map的存储结构,ThreadLocal内部就是使用了线程安全的Map形式的存储把currentThread和变量副本一一映射。
既然要把共享的变成不共享的,那么就要变量满足一个场景:变量的状态不需要共享。例如无状态的bean在多线程之间是安全的,因为线程之间不需要同步bean的状态,用了就走(很不负责啊),想用就用。但是对于有状态的bean在线程之间则必须小心,线程A刚看到状态是a,正想利用a做事情,线程B把bean的状态改为了b,结果做了不该做的。但是如果有状态的bean不需要共享状态,每个线程看到状态a或者b都可以做出自己的行为,这种情况下不同步的选择就是ThreadLocal了。
利用ThreadLocal的优势就在于根本不用担心有状态的bean为了状态的一致而牺牲性能,去使用synchronized限制只有一个线程在同一时间做出关于bean状态的行为。而是多个线程同时根据自己持有的bean的副本的状态做出行为,这样的转变对于并发的支持是那么的不可思议。例如一个Dao内有个Connection的属性,当多个线程使用Dao的同一个实例时,问题就来了:多个线程用一个Connection,而且它还是有连接,关闭等等的状态转变的,我们很敏感的想到这个属性不安全!再看这个属性,其实它是多么的想告诉线程哥哥们:我的这些状态根本就不想共享,不要因为我的状态而不敢一起追求。线程哥哥们也郁闷:你要是有多胞胎姐妹该多好啊!这时候ThreadLocal大哥过来说:小菜,我来搞定!你们这些线程一人一个Connection,你想关就关,想连接就连接,再也不用抱怨说它把你的连接关了。这样Dao的实例再也不用因为自己有个不安全的属性而自卑了。当然ThreadLocal的思路虽然是很好的,但是官方的说法是最初的实现性能并不好,随着Map结构和Thread.currentThread的改进,性能较之synchronized才有了明显的优势。所以要是使用的是JDK1.2,JDK1.3等等,也不要妄想麻雀变凤凰...
再看ThreadLocal和synchronized的本质。前者不在乎多占点空间,但是绝对的忍受不了等待;后者对等待无所谓,但是就是不喜欢浪费空间。这也反映出了算法的一个规律:通常是使用场景决定时间和空间的比例,既省时又省地的算法多数情况下只存在于幻想之中。下面写个简单的例子解释一下,不过个人觉得设计的例子不太好,以后有实际的启发再替换吧。
这个例子中ThreadLocalSample继承自Thread持有OperationSample三个版本中的一个引用,并且在线程运行时执行printAndIncrementNum()方法。
首先看版本1:OperationSample有个共享变量num,printAndIncrementNum()方法没有同步保护,方法就是循环给num赋新值并打印改变值的线程名。因为没有任何的同步保护,所以原本打算每个线程打印出的值是相邻递加10的结果变成了不确定的递加。有可能线程1的循环第一次打印0,第二次就打印50。这时候我们使用被注释的方法声明,结果就是预想的同一个线程的两次结果是相邻的递加,因为同一时刻只有一个线程获得OperationSample实例的隐式锁完成循环释放锁。
再看版本2:假设我们有个递增10的简单计数器,但是是对每个线程的计数。也就是说我们有一个Integer计数器负责每个线程的计数。虽然它是有状态的,会变的,但是因为每个线程之间不需要共享变化,所以可以用ThreadLocal管理这个Integer。在这里看到我们的ThreadLocal变量的initialValue()方法被覆写了,这个方法的作用就是当调用ThreadLocal的get()获取线程绑定的副本时如果还没绑定则调用这个方法在Map中添加当前线程的绑定映射。这里我们返回0,表示每个线程的初始副本在ThreadLocal的Map的纪录都是0。再看printAndIncrementNum()方法,没有任何的同步保护,所以多个线程可以同时进入。但是,每个线程通过threadArg.get()拿到的仅仅是自己的Integer副本,threadArg.set(num + 10)的也是自己的副本值。所以结果就是虽然线程的两次循环打印有快有慢,但是每个线程的两次结果都是0和10。
最后是版本3:和版本2的不同在于新加了一个uniqueId的变量。这个变量是java.util.concurrent.atomic包下的原子变量类。这是基于硬件支持的CAS(比较交换)原语的实现,所以保证了++,--,+=,-=等操作的原子性。所以在ThreadLocal变量的initialValue()方法中使用uniqueId.getAndIncrement()将为每个线程初始化唯一不会重复的递加1的Integer副本值。而结果就会变成5个线程的首次打印是0~4的5个数字,第二次每个线程的打印是线程对应的首次数字加10的值。
对于ThreadLocal的使用,Spring的源码中有大量的应用,主要是要支持Singleton的实例管理,那么自身的一些Singleton的实现内非线程安全的变量,属性要用ThreadLocal隔离共享。同时我们在使用Spring的IOC时也要注意有可能多线程调用的注册到IOC容器的Singleton型实例是否真的线程安全。另外java.util.concurrent.atomic内的原子变量类简单的提了一下,再看看怎么能瞎编出东西来吧。
要实现这样功能类的设计,主要技术点是要能把副本和线程绑定映射,程序可以安全查找到当前线程的副本,修改后安全的绑定给线程。所以我们想到了Map的存储结构,ThreadLocal内部就是使用了线程安全的Map形式的存储把currentThread和变量副本一一映射。
既然要把共享的变成不共享的,那么就要变量满足一个场景:变量的状态不需要共享。例如无状态的bean在多线程之间是安全的,因为线程之间不需要同步bean的状态,用了就走(很不负责啊),想用就用。但是对于有状态的bean在线程之间则必须小心,线程A刚看到状态是a,正想利用a做事情,线程B把bean的状态改为了b,结果做了不该做的。但是如果有状态的bean不需要共享状态,每个线程看到状态a或者b都可以做出自己的行为,这种情况下不同步的选择就是ThreadLocal了。
利用ThreadLocal的优势就在于根本不用担心有状态的bean为了状态的一致而牺牲性能,去使用synchronized限制只有一个线程在同一时间做出关于bean状态的行为。而是多个线程同时根据自己持有的bean的副本的状态做出行为,这样的转变对于并发的支持是那么的不可思议。例如一个Dao内有个Connection的属性,当多个线程使用Dao的同一个实例时,问题就来了:多个线程用一个Connection,而且它还是有连接,关闭等等的状态转变的,我们很敏感的想到这个属性不安全!再看这个属性,其实它是多么的想告诉线程哥哥们:我的这些状态根本就不想共享,不要因为我的状态而不敢一起追求。线程哥哥们也郁闷:你要是有多胞胎姐妹该多好啊!这时候ThreadLocal大哥过来说:小菜,我来搞定!你们这些线程一人一个Connection,你想关就关,想连接就连接,再也不用抱怨说它把你的连接关了。这样Dao的实例再也不用因为自己有个不安全的属性而自卑了。当然ThreadLocal的思路虽然是很好的,但是官方的说法是最初的实现性能并不好,随着Map结构和Thread.currentThread的改进,性能较之synchronized才有了明显的优势。所以要是使用的是JDK1.2,JDK1.3等等,也不要妄想麻雀变凤凰...
再看ThreadLocal和synchronized的本质。前者不在乎多占点空间,但是绝对的忍受不了等待;后者对等待无所谓,但是就是不喜欢浪费空间。这也反映出了算法的一个规律:通常是使用场景决定时间和空间的比例,既省时又省地的算法多数情况下只存在于幻想之中。下面写个简单的例子解释一下,不过个人觉得设计的例子不太好,以后有实际的启发再替换吧。
import java.util.concurrent.atomic.AtomicInteger; /** * User: yanxuxin * Date: Dec 14, 2009 * Time: 9:26:41 PM */ public class ThreadLocalSample extends Thread { private OperationSample2 operationSample; public ThreadLocalSample(OperationSample2 operationSample) { this.operationSample = operationSample; } @Override public void run() { operationSample.printAndIncrementNum(); } public static void main(String[] args) { final OperationSample2 operation = new OperationSample2();//The shared Object for threads. for (int i = 0; i < 5; i++) { new ThreadLocalSample(operation).start(); } } } class OperationSample { private int num; //public synchronized void printAndIncrementNum() { public void printAndIncrementNum() { for (int i = 0; i < 2; i++) { System.out.println(Thread.currentThread().getName() + "[id=" + num + "]"); num += 10; } } } class OperationSample2 { private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public void printAndIncrementNum() { for (int i = 0; i < 2; i++) { int num = threadArg.get(); threadArg.set(num + 10); System.out.println(Thread.currentThread().getName() + "[id=" + num + "]"); } } } class OperationSample3 { private static final AtomicInteger uniqueId = new AtomicInteger(0); private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return uniqueId.getAndIncrement(); } }; public void printAndIncrementNum() { for (int i = 0; i < 2; i++) { int num = threadArg.get(); threadArg.set(num + 10); System.out.println(Thread.currentThread().getName() + "[id=" + num + "]"); } } }
这个例子中ThreadLocalSample继承自Thread持有OperationSample三个版本中的一个引用,并且在线程运行时执行printAndIncrementNum()方法。
首先看版本1:OperationSample有个共享变量num,printAndIncrementNum()方法没有同步保护,方法就是循环给num赋新值并打印改变值的线程名。因为没有任何的同步保护,所以原本打算每个线程打印出的值是相邻递加10的结果变成了不确定的递加。有可能线程1的循环第一次打印0,第二次就打印50。这时候我们使用被注释的方法声明,结果就是预想的同一个线程的两次结果是相邻的递加,因为同一时刻只有一个线程获得OperationSample实例的隐式锁完成循环释放锁。
再看版本2:假设我们有个递增10的简单计数器,但是是对每个线程的计数。也就是说我们有一个Integer计数器负责每个线程的计数。虽然它是有状态的,会变的,但是因为每个线程之间不需要共享变化,所以可以用ThreadLocal管理这个Integer。在这里看到我们的ThreadLocal变量的initialValue()方法被覆写了,这个方法的作用就是当调用ThreadLocal的get()获取线程绑定的副本时如果还没绑定则调用这个方法在Map中添加当前线程的绑定映射。这里我们返回0,表示每个线程的初始副本在ThreadLocal的Map的纪录都是0。再看printAndIncrementNum()方法,没有任何的同步保护,所以多个线程可以同时进入。但是,每个线程通过threadArg.get()拿到的仅仅是自己的Integer副本,threadArg.set(num + 10)的也是自己的副本值。所以结果就是虽然线程的两次循环打印有快有慢,但是每个线程的两次结果都是0和10。
最后是版本3:和版本2的不同在于新加了一个uniqueId的变量。这个变量是java.util.concurrent.atomic包下的原子变量类。这是基于硬件支持的CAS(比较交换)原语的实现,所以保证了++,--,+=,-=等操作的原子性。所以在ThreadLocal变量的initialValue()方法中使用uniqueId.getAndIncrement()将为每个线程初始化唯一不会重复的递加1的Integer副本值。而结果就会变成5个线程的首次打印是0~4的5个数字,第二次每个线程的打印是线程对应的首次数字加10的值。
对于ThreadLocal的使用,Spring的源码中有大量的应用,主要是要支持Singleton的实例管理,那么自身的一些Singleton的实现内非线程安全的变量,属性要用ThreadLocal隔离共享。同时我们在使用Spring的IOC时也要注意有可能多线程调用的注册到IOC容器的Singleton型实例是否真的线程安全。另外java.util.concurrent.atomic内的原子变量类简单的提了一下,再看看怎么能瞎编出东西来吧。
发表评论
文章已被作者锁定,不允许评论。
-
一道位操作的趣味编程题
2010-03-14 10:50 2080看到一道很有意思的编程题:大厅里有64盏灯,每盏灯都编 ... -
一道字符串截取的编程题
2010-03-11 10:52 2272最近接触到一道字符串截取的编程题:编写一个截取字符串的 ... -
一道多线程趣味热身题
2010-02-28 18:01 1912保持对知识点或者技术的熟悉度对于程序员至关重要,要学会 ... -
疑似Google多线程面试题的Java实现
2010-02-24 17:39 4903来到一个完全陌生的地方,即将一切从新开始,内心兴奋又忐 ... -
Mina的线程池实现分析(2)
2010-02-10 17:31 4517分析了I/O事件的存储,下面看看多个Worker同时工 ... -
Mina的线程池实现分析(1)
2010-02-10 17:28 11571线程池是并发应用中,为了减少每个任务调用的开销增强性能 ... -
多线程基础总结十一--ConcurrentLinkedQueue
2010-02-03 17:52 12835ConcurrentLinkedQueue充分使用了a ... -
LinkedBlockingQueue应用--生产消费模型简单实现
2010-01-29 20:45 8132之前介绍时LinkedBlockingQueue提到了 ... -
多线程基础总结十--LinkedBlockingQueue
2010-01-28 14:33 15369随着多线程基础总结的增多,却明显的感觉知道的越来越少, ... -
号称放倒一片的一道J2SE基础题的个人理解
2010-01-23 14:07 2788近日无意中看到一道Java基础题,号称在接受测试的10 ... -
多线程基础总结九--Mina窥探(1)
2010-01-21 23:46 5393一直以来的多线程的基础总结都是脱离应用的,但是要说多线 ... -
多线程基础总结八--ReentrantReadWriteLock
2010-01-15 23:22 7506说到ReentrantReadWriteLock,首先 ... -
多线程基础总结七--ReentrantLock
2010-01-09 23:17 7673之前总结了部分无锁机制的多线程基础,理想的状态当然是利 ... -
关于atomic问题的一点理解
2009-12-30 16:42 2434之前看到一个帖子是关于atomic使用的,当时没有仔细 ... -
多线程基础总结六--synchronized(2)
2009-12-18 18:45 1866早在总结一时,我就尽量的把synchronized的重点 ... -
多线程基础总结五--atomic
2009-12-17 19:46 3545在简单介绍java.util.c ... -
多线程基础总结三--volatile
2009-12-15 20:09 2522前面的两篇总结简 ... -
多线程基础总结二--Thread
2009-12-12 23:27 2663对于Thread来说 ... -
多线程基础总结一--synchronized(1)
2009-12-12 23:23 3066最近写关于并发的小应 ... -
由destory-method引发的IOC容器设计的思考
2009-12-07 16:51 1678第一次读Spring的源 ...
相关推荐
多线程 278 ThreadLocal 285 进程 vs. 线程 289 分布式进程 292 正则表达式 298 常用内建模块 304 datetime 305 collections 312 base64 317 struct 320 hashlib 322 itertools 327 contextlib 331 XML...
【多线程】Java四种线程池的创建方法 83 【多线程】线程池原理和运行机制 83 【多线程】线程池对任务的处理 85 【多线程】线程池的状态 86 线程池的状态说明 86 各个状态之间的转换 86 【多线程】什么是线程池?如果...
12.2 多线程 225 12.3 ThreadLocal 232 12.4 进程 vs. 线程 235 12.5 分布式进程 237 13 正则表达式 243 13.1 常用内建模块 249 13.1.1 datetime 249 13.1.2 collections 254 13.1.3 base64 258 13.1.4 struct 260 ...
多线程 ThreadLocal 进程 vs. 线程 分布式进程 正则表达式 常用内建模块 datetime collections base64 struct hashlib hmac itertools contextlib urllib XML HTMLParser 常用第三方模块 Pillow requests chardet ...
│ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java.htm │ │ │ SCJP 1_4 认证的初级教程.htm │ │ │ 新建 文本文档....
│ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java.htm │ │ │ SCJP 1_4 认证的初级教程.htm │ │ │ 新建 文本文档....
│ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java.htm │ │ │ SCJP 1_4 认证的初级教程.htm │ │ │ 新建 文本文档....
│ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java.htm │ │ │ SCJP 1_4 认证的初级教程.htm │ │ │ 新建 文本文档....
│ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java.htm │ │ │ SCJP 1_4 认证的初级教程.htm │ │ │ 新建 文本文档....
10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...
10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...
{8}多线程}{121}{chapter.8} {8.1}线程的常用属性与方法}{121}{section.8.1} {8.2}后台线程}{123}{section.8.2} {8.3}创建线程的两种方法}{123}{section.8.3} {8.4}Runnable}{123}{section.8.4} {8.5}Sleep...