ThreadLocal的相关知识
ThreadLocal知识
在线程编程中必须注意对临界资源的处理,当多个线程读取临界资源时,应当对资源加锁,防止访问冲突。在java编程中,多线程访问共享资源的时候,也需要加锁保证线程访问安全,一般使用synchronized关键字标示。但加锁有一个很大的缺点,影响执行速度,因为加锁的区域只能有单一线程访问。所以,一般对于需要加锁的区域,都尽量使得代码区域越小越好,通过提高锁的粒度来使得同步造成的性能影响尽可能小。
但其实如果能够将临界资源变成非临界资源,那么我们就不需要对资源加锁,当然同步所造成的性能影响就消失了。比如数据库连接Connection,如果我们可以保证每个线程都有一个属于自己的Connection,多线程访问Connection都只访问属于自己的连接,Connection就不在需要同步了。如果每个线程都有一个Connection,那么是否需要对每个线程的Connection定义不同的名称呢,显然这样的编程方式很不灵活(对多线程执行的代码需要传递额外的判断参数或进行额外的判断)。
Java中的ThreadLocal可以保证每个线程拥有一份数据副本,同时访问的时候又不需要额外判断数据属于哪个线程,每个线程都使用统一的名称访问属于自己的那份数据副本。
ThreadLocal中方法:
T get() // 返回此线程局部变量的当前线程副本中的值。
protected T initialValue() //返回此线程局部变量的当前线程的“初始值”。
Note : initialValue方法是protected的,也就是说我们可以覆盖该方法(一般采用匿名类的方式),提供给ThreadLocal不同的初始值,在ThreadLocal中,默认的initialValue返回的值是null(这个值可能不能满足我们对初始值的需求)。
void remove() // 移除此线程局部变量当前线程的值。
void set(T value) //将此线程局部变量的当前线程副本中的值设置为指定值。
ThreadLocal使用方法:
比如对于Connection,可以定义:static ThreadLocal<Connection> connTreadLocal = new ThreadLoca<Connection>();
对于方法:getConnection(),这个方法可以被多个线程访问,使用了ThreadLocal,就可以不用对这个方法加锁了,具体方法可以如下:
public static Connection getConnection() {
if(coonThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connTreadLocal.set(conn);
return conn;
} else {
return connThreadLocal.get();
}
}
ThreadLocal的原理
在ThreadLocal中有一个类:ThreadLocalMap,这个类就是用来存储一个线程中的所有ThreadLocal变量的,在Thread类中,可以看到这样的变量定义:ThreadLocal.ThreadLocalMapthreadLocals = null;正是由threadLocals来保存一个线程中所有的ThreadLocal变量。在ThreadLocalMap中,是采用一个Entry数组:
Entry[] table来保存线程的所有ThreadLocal变量的。Entry的构造函数是:private Entry(ThreadLocal k, Object v),也就是每个Entry都是把ThreadLocal对象本事做为一个key,ThreadLocal的值作为value。
在ThreadLocal中void T set()的实现代码是:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// getMap(t)实际是返回t中的 threadLocals(上面提到了Thread中有个
// ThreadLocalMap的变量threadLocals)
if (map != null) // 如果map存在,直接往里面放数据
map.set(this, value);
else
createMap(t, value); // 否则先创建,然后放入数据
}
在ThreadLocal中public T get()方法实现代码是:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 可以看出,查找值的时候正是以ThreadLocal对象作为key进行查找的(可以参考Entry的
// 构造函数),实际查找是到ThreadLocalMap中存放数据的Entry[] table中进行的
if (map != null)
// 当map.get(this)找不到值时,会创建一个以this作为key的Entry,同时初始值为
// initialValue(),并返回这个initialValue值
return (T)map.get(this);
T value = initialValue();
createMap(t, value);
return value;
}
由于每个线程的所有ThreadLocal数据均是放在ThreadLocalMap中的,故当调用public T set()的时候,实际上是会调用ThreadLocalMap的set(ThreadLocal key, Object value),ThreadLocalMap中的这个方法如下:
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i, false);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
可以看出在设值的时候,使用了hash的方式找一个位置,然后在这个位置i上设置,但既然是hash的方式,就有可能导致hash冲突,可以看出当冲突的时候,代码采用了简单的线性查找的方式找一个可用的位置,来存放数据的。采用线性的方式如何防止更多的hash冲突,可能与ThreadLocal设置的hashcode方式有关吧(还不是很明白其中的数学道理)。每个ThreadLocal都有一个hashcode,并且对于不同的ThreadLocal的hashcode,增长的方式是:
HASH_INCREMENT = 0x61c88647; // 为什么是设置这个间隔值,应该有数学上的原理吧
nextHashCode = h + HASH_INCREMENT;
总结:
使用ThreaLocal是一种利用空间将临界资源变成非临界资源的一种方式,同时它可以使得多线程以统一的方式访问自已线程的变量副本,减少了多线程编程中的参数传递。但是,它并不能解决多线程中共享资源访问的加锁问题。对于多个线程均需要访问的共享资源,比如只有一台打印机等,仍然需要考虑访问冲突。如果按照ThreadLocal的方式,那就应该给每个线程分派一台打印机。