在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。本文将对ThreadLocal源代码分析
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> 源代码为android sdk 19.如有不对或者错误的望指出。</span>
首先在TheadLocal 中有一个重要的变量来保存与相关的值。在API 19 中这个变量叫Value。请看源码:
<span style="background-color: rgb(255, 255, 255);"> /**
* Normal thread local values.
*/
ThreadLocal.Values localValues;</span>
等下我们来详细分析这个类。现在先回到ThreadLocal 两个经常用到的两个方法:set(T value)和get()。由浅入深,先分析get():
/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
这个程序大概的意思我想大家都能看懂,通过源代码发现,查找值是通过value.table这个数组通过索引值来找到值得。其实value.table 数组存取键值对是这样一种情况:如果键值的key的索引为index,则所对应到的value索引为index+1. 由此分析可知 hash&values.mask 获取的就是key的索引值。values.mask=values.table.length-1;从源代码注释也可以看到
/**
* Internal hash. We deliberately don't bother with #hashCode().
* Hashes must be even. This ensures that the result of
* (hash & (table.length - 1)) points to a key and not a value.
*
* We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
* every other bucket) to help prevent clustering.
*/
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
在上面几行代码中,我们隐约的发现了:key值并不是ThreadLocal 本身,而是reference 这个变量。我们继续来看源码:
/** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference
= new WeakReference<ThreadLocal<T>>(this);
发现这是ThreadLocal的弱引用。这是为什么呢?因为开启线程的开销比较大,所以我们经常会把线程放入线程池中来对线程进行重用。如果不采用弱引用的方式,那线程自身所带的数据都无法释放。这会造成内存紧张,容易发生OOM。
接下来分析 set(T value)
<pre name="code" class="java">
* * @param value the new value of the variable for the caller thread. */ public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } 程序比较简单,重点来分析put方法。
/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
大致意思比较简单:通过hash 值定位到key的索引值,然后判断这个索引值是否有对应的TheadLocal;如果有,则返回value。如果没有,通过找到firstTombstone这个索引值,然后赋值对应的key和value。在这里有人要问了这个firstTombstone是什么意思?别着急,慢慢分析。接下我们分析cleanup(),根据字面意思它是清理内存的作用,让我们来看源代码:
/**
* Cleans up after garbage-collected thread locals.
*/
private void cleanUp() {
if (rehash()) {
// If we rehashed, we needn't clean up (clean up happens as
// a side effect).
return;
}
if (size == 0) {
// No live entries == nothing to clean.
return;
}
// Clean log(table.length) entries picking up where we left off
// last time.
int index = clean;
Object[] table = this.table;
for (int counter = table.length; counter > 0; counter >>= 1,
index = next(index)) {
Object k = table[index];
if (k == TOMBSTONE || k == null) {
continue; // on to next entry
}
// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
if (reference.get() == null) {
// This thread local was reclaimed by the garbage collector.
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
}
}
// Point cursor to next index.
clean = index;
}
从代码可以知道,ThreadLocal 对象被回收了,但是它的弱引用所对应的value却还存在,通过value=null来释放value所占用的内存。并给它设置一个标志用TOMBSTONE。表示这块内存已经被释放了。
这样上面 firstTombstone 这个字段的含义也一目了然了。
到这里我们思考一下,既然使用数组来保存对象,数组肯定要扩容啊,没错,确实有这个方法,叫rehash()
/**
* Rehashes the table, expanding or contracting it as necessary.
* Gets rid of tombstones. Returns true if a rehash occurred.
* We must rehash every time we fill a null slot; we depend on the
* presence of null slots to end searches (otherwise, we'll infinitely
* loop).
*/
private boolean rehash() {
if (tombstones + size < maximumLoad) {
return false;
}
int capacity = table.length >> 1;
// Default to the same capacity. This will create a table of the
// same size and move over the live entries, analogous to a
// garbage collection. This should only happen if you churn a
// bunch of thread local garbage (removing and reinserting
// the same thread locals over and over will overwrite tombstones
// and not fill up the table).
int newCapacity = capacity;
if (size > (capacity >> 1)) {
// More than 1/2 filled w/ live entries.
// Double size.
newCapacity = capacity * 2;
}
Object[] oldTable = this.table;
// Allocate new table.
initializeTable(newCapacity);
// We won't have any tombstones after this.
this.tombstones = 0;
// If we have no live entries, we can quit here.
if (size == 0) {
return true;
}
// Move over entries.
for (int i = oldTable.length - 2; i >= 0; i -= 2) {
Object k = oldTable[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}
// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
if (key != null) {
// Entry is still live. Move it over.
add(key, oldTable[i + 1]);
} else {
// The key was reclaimed.
size--;
}
}
return true;
}
就是当前正在使用的内存大于分配内存的1/2的时候进行数扩容。
还有最后一个方法就是remove了,用来释放内存。
<pre name="code" class="java"> /**
* Removes entry for the given ThreadLocal.
*/
void remove(ThreadLocal<?> key) {
cleanUp();
for (int index = key.hash & mask;; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
// Success!
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
return;
}
if (reference == null) {
// No entry found.
return;
}
}
}
通过代码分析也可以知道,也用TOMBSTONE来表示这块内存已经被释放掉了,有兴趣的朋友可以仔细研究代码。
最后分析下本篇文章最长用到的一个函数:
<pre name="code" class="java"> /**
* Gets the next index. If we're at the end of the table, we wrap back
* around to 0.
*/
private int next(int index) {
return (index + 2) & mask;
}
这方法的好处是可以循环遍历,嗯 ,以后借鉴这种方法也是不错的。
本人最后提醒一下:1 使用ThreadLocal 要真正实行线程之间数据无干扰的话,必须要进行对象深拷贝。
2 使用ThreadLocal 要记得在恰当的时机remove