- 浏览: 132094 次
- 性别:
- 来自: ...
文章分类
最新评论
说到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 2124看到一道很有意思的编程题:大厅里有64盏灯,每盏灯都编 ... -
一道字符串截取的编程题
2010-03-11 10:52 2321最近接触到一道字符串截取的编程题:编写一个截取字符串的 ... -
一道多线程趣味热身题
2010-02-28 18:01 1960保持对知识点或者技术的熟悉度对于程序员至关重要,要学会 ... -
疑似Google多线程面试题的Java实现
2010-02-24 17:39 4965来到一个完全陌生的地方,即将一切从新开始,内心兴奋又忐 ... -
Mina的线程池实现分析(2)
2010-02-10 17:31 4590分析了I/O事件的存储,下面看看多个Worker同时工 ... -
Mina的线程池实现分析(1)
2010-02-10 17:28 11633线程池是并发应用中,为了减少每个任务调用的开销增强性能 ... -
多线程基础总结十一--ConcurrentLinkedQueue
2010-02-03 17:52 12952ConcurrentLinkedQueue充分使用了a ... -
LinkedBlockingQueue应用--生产消费模型简单实现
2010-01-29 20:45 8213之前介绍时LinkedBlockingQueue提到了 ... -
多线程基础总结十--LinkedBlockingQueue
2010-01-28 14:33 15443随着多线程基础总结的增多,却明显的感觉知道的越来越少, ... -
号称放倒一片的一道J2SE基础题的个人理解
2010-01-23 14:07 2847近日无意中看到一道Java基础题,号称在接受测试的10 ... -
多线程基础总结九--Mina窥探(1)
2010-01-21 23:46 5455一直以来的多线程的基础总结都是脱离应用的,但是要说多线 ... -
多线程基础总结八--ReentrantReadWriteLock
2010-01-15 23:22 7559说到ReentrantReadWriteLock,首先 ... -
多线程基础总结七--ReentrantLock
2010-01-09 23:17 7731之前总结了部分无锁机制的多线程基础,理想的状态当然是利 ... -
关于atomic问题的一点理解
2009-12-30 16:42 2487之前看到一个帖子是关于atomic使用的,当时没有仔细 ... -
多线程基础总结六--synchronized(2)
2009-12-18 18:45 1913早在总结一时,我就尽量的把synchronized的重点 ... -
多线程基础总结五--atomic
2009-12-17 19:46 3595在简单介绍java.util.c ... -
多线程基础总结三--volatile
2009-12-15 20:09 2584前面的两篇总结简 ... -
多线程基础总结二--Thread
2009-12-12 23:27 2712对于Thread来说 ... -
多线程基础总结一--synchronized(1)
2009-12-12 23:23 3118最近写关于并发的小应 ... -
由destory-method引发的IOC容器设计的思考
2009-12-07 16:51 1726第一次读Spring的源 ...
相关推荐
### 多线程基础 #### 1. 多线程概念 多线程是指在一个程序中包含多个控制流,它们可以并发执行不同的任务。在Java中,多线程的实现通常借助于`Thread`类或实现`Runnable`接口。多线程能够提高CPU的利用率,改善应用...
1. **线程基础** - 线程是操作系统分配CPU时间的基本单位,一个进程可以包含一个或多个线程。 - Java中通过`java.lang.Thread`类或者实现`Runnable`接口来创建线程。 - 主线程:每个Java应用程序都有一个主线程,...
在IT行业中,多线程是实现高...总结来说,多线程基础部分的学习涵盖了线程创建、线程状态、线程同步、ThreadLocal的使用以及并发编程的高级概念。通过深入理解这些内容,开发者可以编写出更加高效、稳定的多线程程序。
在C#编程环境中,开发Windows...总结,C#中的WinForm和WinCE多线程编程涉及创建线程、同步机制、UI更新、异常处理和性能优化等多个方面。通过深入理解这些概念和实践,开发者可以构建出高效、响应性的跨平台应用程序。
一、Java多线程基础 多线程是指在单个应用程序中同时执行多个线程的能力。在Java中,我们可以通过实现Runnable接口或继承Thread类来创建线程。通过多线程,可以将不同的任务分配到不同的线程中,从而实现任务的并行...
以下是对Java多线程系列文章的详细知识点总结: 1. **线程基础** - **线程的概念**:线程是操作系统分配CPU时间的基本单位,每个线程拥有独立的程序计数器、寄存器和栈。 - **线程创建**:Java提供了两种创建线程...
总结来说,Java多线程技术与HTTP断点续传相结合,能够提供高效且用户友好的文件下载服务。但同时,我们必须重视线程安全问题,采用适当的同步机制来保护共享资源,以确保程序的稳定性和正确性。在实际开发中,理解并...
总结,Java多线程与线程安全在实现HTTP协议的断点续传中起着关键作用,通过合理的线程管理和并发控制,能够实现高效且可靠的文件传输。在实际项目中,应结合具体需求,灵活运用各种并发工具和策略,确保程序的稳定性...
然而,尽管Java程序员普遍了解线程的基础概念,但在项目实践中,尤其是在复杂场景下处理多线程问题时,往往暴露出对多线程技术掌握不深、理解不透彻的问题。因此,企业常将Java线程技术作为衡量程序员基础技能和编码...
#### 一、Java多线程基础 在Java中,多线程编程是一项非常重要的技术,它能够有效地提高程序的执行效率,并行处理多个任务。多线程编程的核心在于理解线程的创建方式、生命周期以及线程间的通信。 #### 二、线程...
- **目标读者**: 本教程主要面向具备丰富Java基础知识但缺乏多线程编程经验的学习者。 - **学习成果**: 学习者能够掌握编写简单的多线程程序的能力,并能够理解和分析使用线程的基本程序。 #### 二、线程基础知识 -...
1. **多线程基础** - **线程与进程**:线程是进程中的执行单元,一个进程可以有多个线程。每个线程都有自己独立的程序计数器、系统栈、局部变量,但共享同一块内存空间。 - **主线程与工作线程**:ASP.NET应用程序...
### Java多线程知识点详解 #### 一、Java线程:...Java多线程编程涉及到的概念广泛,从基础的线程创建到高级的线程池管理和锁控制,每一步都需要深入理解。掌握这些知识点对于开发高效、可靠的多线程应用至关重要。
一、Java多线程基础 1. 线程的创建:Java提供了两种创建线程的方式——继承Thread类和实现Runnable接口。继承Thread类可以直接创建一个新的线程类,而实现Runnable接口则可以将线程逻辑封装在任何类中,更利于代码...
线程安全是Java多线程编程中的一个重要概念,确保在多线程环境下程序能够正确地运行,避免数据错误和资源竞争等问题。本文档旨在从基础概念出发,详细介绍导致线程不安全的原因、如何避免线程不安全现象的发生,并...
Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....
Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....
Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....
Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....