最近看了Spring和Hibernate的源码,发现大量使用了ThreadLocal,于是上网学习了一些关于ThreadLocal的文章,将自己的学习小结贴上来,大家一起进步!
1.ThreadLocal用来解决多线程程序的并发问题
2.ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护
变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都
可以独立地改变自己的副本,而不会影响其它线程所对应的副本.
3.从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要
表达的意思。
4.线程局部变量并不是Java的新发明,Java没有提供在语言级支持(语法上),而是变
相地通过ThreadLocal的类提供支持.
5.ThreadLocal类中的方法:(JDK5版本之后支持泛型)
void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值
void remove()
移除此线程局部变量当前线程的值
protected T initialValue()
返回此线程局部变量的当前线程的“初始值”
T get()
返回此线程局部变量的当前线程副本中的值
6.ThreadLocal的原理:
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很
简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素
的键为线程对象,而值对应线程的变量副本
7.自己模拟ThreadLocal:
public class SimpleThreadLocal{
private Map valueMap=Collections.synchronizedMap(new HashMap());
public void set(Object newValue){
valueMap.put(Thread.currentThread(),newValue);//键为线程对象,值为本线程的变量副本
}
public Object get(){
Thread currentThread=Thread.currentThread();
Object o=valueMap.get(currentThread);//返回本线程对应的变量
if(o==null&&!valueMap.containsKey(currentThread)){
//如果在Map中不存在,放到Map中保存起来
o=initialValue();
valueMap.put(currentThread,o);
}
return o;
}
public void remove(){
valueMap.remove(Thread.currentThread());
}
public void initialValue(){
return null;
}
}
8.使用ThreadLocal的具体例子:
public class SequenceNumber{
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seNum=new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0;
}
}
public Integer getNextNum(){
seNum.set(seNum.get()+1);
return seNum.get();
}
public static void main(String[] args){
SequenceNumber sn=new SequenceNumber();
//3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
this.sn=sn;
}
public void run(){
//每个线程打印3个序列号
for(int i=0;i<3;i++){
System.out.println("thread["+Thread.currentThread().getName()+",sn["+sn.getNextNum()+"]");
}
}
}
}
解析:通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量
值.
考察输出的结果信息(将上段代码在IDE运行),我们发现每个线程所产生的序号虽然都共享同一个
SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序
列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
9.ThreadLocal和同步机制的比较:
相同点:ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲
突问题
区别:在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。
这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量
进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序
设计和编写难度相对较大。
ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每
一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为
每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变
量封装进ThreadLocal.
概括来说:对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方
式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同
的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互
不影响。
总结:ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变
量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题
更简单,更方便,且结果程序拥有更高的并发性。
一、对ThreadLocal概术
JDK API 写道:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
二、结合源码理解
可以看到ThreadLocal类中的变量只有这3个int型:
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647 ;
ThreadLocal实例的变量只有 threadLocalHashCode
ThreadLocal类的静态变量nextHashCode 和HASH_INCREMENT
实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的 threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。
而nextHashCode()方法就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。
看一下set()方法: 获取当前线程的引用,从map中获取该线程对应的map,如果map存在更新缓存值,否则创建并存储
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
再来看一下get()方法: 首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量。
调用get方法如果此Map不存在首先初始化,创建此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,我们可以覆盖此方法,在首次调用时初始化一个适当的值,默认是null
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();
}
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;
}
protected T initialValue() {
return null;
}
我们来看下ThreadLocalMap静态内部类,在ThreadLocalMap 内部的Entry是WeakReference
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
。。。。。
ThreadLocal和多线程并发没有什么关系。ThreadLocal模式是为了解决单线程内的跨类跨方法调用的
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
三、例子
引用Tim Cull的博文“SimpleDateFormat: Performance Pig”介绍下ThreadLocal的简单使用,同时也对SimpleDateFormat的使用有个深入的了解。
Tim Cull 写道:
Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个 SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。即使将 SimpleDateFormat定义为静态类变量,貌似能解决这个问题,但是SimpleDateFormat是非线程安全的,同样存在问题,如果用 ‘synchronized’线程同步同样面临问题,同步导致性能下降(线程之间序列化的获取SimpleDateFormat实例)。
Tim Cull使用Threadlocal解决了此问题,对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本
import
java.text.DateFormat;
import
java.text.ParseException;
import
java.text.SimpleDateFormat;
import
java.util.Date;
/**
* 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。
* @author
*
*/
public
class
DateUtil
{
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
@SuppressWarnings("rawtypes")
private static ThreadLocal threadLocal = new ThreadLocal() {
protected synchronized Object initialValue() {
return new SimpleDateFormat(DATE_FORMAT);
}
};
public static DateFormat getDateFormat() {
return (DateFormat) threadLocal.get();
}
public static Date parse(String textDate) throws ParseException {
return getDateFormat().parse(textDate);
}
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。
* @author
*
*/
public class DateUtil {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
@SuppressWarnings("rawtypes")
private static ThreadLocal threadLocal = new ThreadLocal() {
protected synchronized Object initialValue() {
return new SimpleDateFormat(DATE_FORMAT);
}
};
public static DateFormat getDateFormat() {
return (DateFormat) threadLocal.get();
}
public static Date parse(String textDate) throws ParseException {
return getDateFormat().parse(textDate);
}
}
创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建
private static ThreadLocal threadLocal = new ThreadLocal();
// 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中
public static DateFormat getDateFormat()
{
DateFormat df = (DateFormat) threadLocal.get();
if(df==null){
df = new SimpleDateFormat(DATE_FORMAT)
threadLocal.set(df);
}
return df;
}